In this lab, we are going to work with vector data, which we’ve talked about last week. We are going to work with a dataset of cancer patients across California, as well as Census data on socioeconomic factors. We will also talk about geocoding addresses and will discuss how to visualize data.

The objectives of this guide are to teach you:

  1. How to geocode addresses
  2. How to bring in and visualize point data
  3. How to download Census data using the Census API
  4. How to conduct exploratory data analysis

Let’s get cracking!


Open up an R Markdown file

We hopefully remember some of this from last week in Lab 1, but let’s open an R Markdown file by clicking on File at the top menu in RStudio, select New File, and then R Markdown…. A window should pop up. In that window, for title, put in “Lab 2”. For author, put your name. Leave the HTML radio button clicked, and select OK. A new R Markdown file should pop up in the top left window.


What packages do we need?

Let’s load some packages that we will need this week. We need to load any packages we previously installed using the function library(). Remember, install once, load every time. And if it gives you an error for no package called..., then we need to install those packages using install.packages(). So when using a package, library() should always be at the top of your R Markdown.

library(tidygeocoder)
library(sf)
library(MapGAM)
library(tidyverse)
library(tidycensus)
library(flextable)
library(tmap)


Geocoding

First, we are going to tackle how we take addresses and convert them to spatial data (latitude and longitude). So, let’s say we wanted to map all of the marijuana dispensaries across San Francisco. Let’s download a .csv of these addresses from the Github site, then take a look at the dataset.

download.file("https://raw.githubusercontent.com/pjames-ucdavis/SPH215/refs/heads/main/san_francisco_active_marijuana_retailers.csv", "san_francisco_active_marijuana_retailers.csv", mode = "wb")

sf_mj <- read_csv("san_francisco_active_marijuana_retailers.csv")
## Rows: 33 Columns: 10
## ── Column specification ───────────────────────────────────
## Delimiter: ","
## chr (10): License Number, License Type, Business Owner, Business Structure, ...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(sf_mj)                  
## # A tibble: 6 × 10
##   `License Number` `License Type`          `Business Owner` `Business Structure`
##   <chr>            <chr>                   <chr>            <chr>               
## 1 C10-0000614-LIC  Cannabis - Retailer Li… Terry Muller     Limited Liability C…
## 2 C10-0000586-LIC  Cannabis - Retailer Li… Jeremy Goodin    Corporation         
## 3 C10-0000587-LIC  Cannabis - Retailer Li… Justin Jarin     Corporation         
## 4 C10-0000539-LIC  Cannabis - Retailer Li… Ondyn Herschelle Corporation         
## 5 C10-0000522-LIC  Cannabis - Retailer Li… Ryan Hudson      Limited Liability C…
## 6 C10-0000523-LIC  Cannabis - Retailer Li… Ryan Hudson      Limited Liability C…
## # ℹ 6 more variables: `Premise Address` <chr>, Status <chr>,
## #   `Issue Date` <chr>, `Expiration Date` <chr>, Activities <chr>,
## #   `Adult-Use/Medicinal` <chr>


OK, some interesting columns there, and we have Premise Address as a column that we might want to make spatial. Let’s look closer at that.

head(sf_mj$`Premise Address`)
## [1] "2165 IRVING ST san francisco, CA 94122 County: SAN FRANCISCO" 
## [2] "122 10TH ST SAN FRANCISCO, CA 941032605 County: SAN FRANCISCO"
## [3] "843 Howard ST SAN FRANCISCO, CA 94103 County: SAN FRANCISCO"  
## [4] "70 SECOND ST SAN FRANCISCO, CA 94105 County: SAN FRANCISCO"   
## [5] "527 Howard ST San Francisco, CA 94105 County: SAN FRANCISCO"  
## [6] "2414 Lombard ST San Francisco, CA 94123 County: SAN FRANCISCO"


OK that column looks like what we want to geocode. But how do we take these addresses and make them into spatial information? We have to geocode them! To do so, we will use the tidygeocoder package in R. But first, we see that the addresses look a little strange. The address county is always “County: SAN FRANCISCO” so we will gsub() out that entire string.

sf_mj$`Premise Address` <- gsub(" County: SAN FRANCISCO",
                                  "", sf_mj$`Premise Address`)
head(sf_mj$`Premise Address`)
## [1] "2165 IRVING ST san francisco, CA 94122" 
## [2] "122 10TH ST SAN FRANCISCO, CA 941032605"
## [3] "843 Howard ST SAN FRANCISCO, CA 94103"  
## [4] "70 SECOND ST SAN FRANCISCO, CA 94105"   
## [5] "527 Howard ST San Francisco, CA 94105"  
## [6] "2414 Lombard ST San Francisco, CA 94123"

That looks much better.


Now let’s give a try to geocoding these addresses with the tidygeocoder package. We will use the geocode() function to add a latitude and longitude to each of our addresses in the Premise Address column. We will use the Open Street Map address database by specifying method = "osm". This will take about a minute to run, so be patient!

sf_mj_geo      <- geocode(sf_mj, "Premise Address",
                          method = "osm")
## Passing 33 addresses to the Nominatim single address geocoder
## Query completed in: 41 seconds
head(sf_mj_geo)
## # A tibble: 6 × 12
##   `License Number` `License Type`          `Business Owner` `Business Structure`
##   <chr>            <chr>                   <chr>            <chr>               
## 1 C10-0000614-LIC  Cannabis - Retailer Li… Terry Muller     Limited Liability C…
## 2 C10-0000586-LIC  Cannabis - Retailer Li… Jeremy Goodin    Corporation         
## 3 C10-0000587-LIC  Cannabis - Retailer Li… Justin Jarin     Corporation         
## 4 C10-0000539-LIC  Cannabis - Retailer Li… Ondyn Herschelle Corporation         
## 5 C10-0000522-LIC  Cannabis - Retailer Li… Ryan Hudson      Limited Liability C…
## 6 C10-0000523-LIC  Cannabis - Retailer Li… Ryan Hudson      Limited Liability C…
## # ℹ 8 more variables: `Premise Address` <chr>, Status <chr>,
## #   `Issue Date` <chr>, `Expiration Date` <chr>, Activities <chr>,
## #   `Adult-Use/Medicinal` <chr>, lat <dbl>, long <dbl>


Hmm, looks like some of our addresses have an NA for their lat and long. Let’s take a closer look.

summary(sf_mj_geo$lat)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   37.71   37.75   37.78   37.77   37.78   37.80      10
summary(sf_mj_geo$long)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##  -122.5  -122.4  -122.4  -122.4  -122.4  -122.4      10


Looks like we have 10 addresses missing lat and 10 missing long. Let’s try this again using a different geocoding database called arcgis.

sf_mj_geo_arc      <- geocode(sf_mj, "Premise Address",
                          method = "arcgis")
## Passing 33 addresses to the ArcGIS single address geocoder
## Query completed in: 30.1 seconds
head(sf_mj_geo_arc)
## # A tibble: 6 × 12
##   `License Number` `License Type`          `Business Owner` `Business Structure`
##   <chr>            <chr>                   <chr>            <chr>               
## 1 C10-0000614-LIC  Cannabis - Retailer Li… Terry Muller     Limited Liability C…
## 2 C10-0000586-LIC  Cannabis - Retailer Li… Jeremy Goodin    Corporation         
## 3 C10-0000587-LIC  Cannabis - Retailer Li… Justin Jarin     Corporation         
## 4 C10-0000539-LIC  Cannabis - Retailer Li… Ondyn Herschelle Corporation         
## 5 C10-0000522-LIC  Cannabis - Retailer Li… Ryan Hudson      Limited Liability C…
## 6 C10-0000523-LIC  Cannabis - Retailer Li… Ryan Hudson      Limited Liability C…
## # ℹ 8 more variables: `Premise Address` <chr>, Status <chr>,
## #   `Issue Date` <chr>, `Expiration Date` <chr>, Activities <chr>,
## #   `Adult-Use/Medicinal` <chr>, lat <dbl>, long <dbl>
summary(sf_mj_geo_arc$lat)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   37.71   37.76   37.77   37.77   37.78   37.80
summary(sf_mj_geo_arc$long)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  -122.5  -122.4  -122.4  -122.4  -122.4  -122.4


Woohoo! No missingness. Love to see it. OK, let’s plot these data and see how they look.

plot(sf_mj_geo_arc$long, sf_mj_geo_arc$lat)


We are in business! We have taken addresses and converted them into latitude and longitude! I think we need a badge! tidygeocoder Badge


Bonus exercise! Let’s take these addresses and reverse geocode them. That’s just a fancy way of saying that we will take latitude and longitude data and convert it into readable addresses. We use the aptly named function reverse_geocode and specify which columns to look at (lat and long), the method we want for geocoding, and what we want the address column to be named. Then we select out some columns that we aren’t really interested in. Remember, we are doing this the tidy way so we are using %>% pipes.

reverse <- sf_mj_geo_arc %>%
  reverse_geocode(lat = lat, long = long, method = 'arcgis',
                  address = address_found) %>%
  select(-`Business Owner`,-`Business Structure`,-`License Number`,-`License Type`,-Status,-`Issue Date`,-`Expiration Date`,-Activities,-`Adult-Use/Medicinal`)
## Passing 33 coordinates to the ArcGIS single coordinate geocoder
## Query completed in: 26.6 seconds
head(reverse)
## # A tibble: 6 × 4
##   `Premise Address`                         lat  long address_found             
##   <chr>                                   <dbl> <dbl> <chr>                     
## 1 2165 IRVING ST san francisco, CA 94122   37.8 -122. Smokin D's BBQ, 2181 Irvi…
## 2 122 10TH ST SAN FRANCISCO, CA 941032605  37.8 -122. Urbana Bay Area, 122 10th…
## 3 843 Howard ST SAN FRANCISCO, CA 94103    37.8 -122. Green Door SF, 843 Howard…
## 4 70 SECOND ST SAN FRANCISCO, CA 94105     37.8 -122. Cannavine San Francisco, …
## 5 527 Howard ST San Francisco, CA 94105    37.8 -122. Fixed, 527 Howard St, Ste…
## 6 2414 Lombard ST San Francisco, CA 94123  37.8 -122. Mile 7.6 Us Hwy 101 N, Sa…

Looking at Premise Address and address_found we can see that we did pretty well! Not perfect, but most are the right address or a few doors down. Well done!


sf: Our go to package for vector data

Although there are a few ways to work with vector spatial data in R, we will focus on the sf package in this course. The majority of spatial folks in R have shifted to sf for vector data, and so it makes sense to focus on it in the class.

Processing spatial data is very similar to nonspatial data thanks to the package sf, which is tidy friendly. sf stands for simple features. The Simple Features standard defines a simple feature as a representation of a real world object by a point or points that may or may not be connected by straight line segments to form lines or polygons. A feature is thought of as a thing, or an object in the real world, such as a building or a tree. A county can be a feature. As can a city and a neighborhood. Features have a geometry describing where on Earth the features are located, and they have attributes, which describe other properties.

Now let’s get our hands dirty working with some spatial data.


Vectors: Import Cancer Point Data

For this lab, we will primarily be working with the MapGAM package. If you go to the link, you can read the reference manual on the various datasets available in the package. For this lab, we will mainly be working with the CAdata dataset. While they are based on real patterns expected in observational epidemiologic studies, these data have been simulated and are for teaching purposes only. The data contain 5000 simulated ovarian cancer cases. This is a cohort with time to mortality measured, but for the purposes of our class, we will conduct simple tabular analyses looking at associations between spatial exposures with mortality at end of follow-up.

The CAdata dataset contains the following variables

  • time (follow-up time to either event of being censored)
  • event (1=dead, 0=censored)
  • X (Latitude)
  • Y (Longitude)
  • AGE (age in years)
  • INS (insurance status, categorical)


So let’s bring in the CAdata dataset and have a look at it.

#Load CAdata dataset from MapGAM package
data(CAdata)
ca_pts <- CAdata
summary(ca_pts)
##       time               event              X                 Y          
##  Min.   : 0.004068   Min.   :0.0000   Min.   :1811375   Min.   :-241999  
##  1st Qu.: 1.931247   1st Qu.:0.0000   1st Qu.:2018363   1st Qu.: -94700  
##  Median : 4.749980   Median :1.0000   Median :2325084   Median : -60386  
##  Mean   : 6.496130   Mean   :0.6062   Mean   :2230219   Mean   :  87591  
##  3rd Qu.: 9.609031   3rd Qu.:1.0000   3rd Qu.:2380230   3rd Qu.: 318280  
##  Max.   :24.997764   Max.   :1.0000   Max.   :2705633   Max.   : 770658  
##       AGE         INS      
##  Min.   :25.00   Mcd: 431  
##  1st Qu.:53.00   Mcr:1419  
##  Median :62.00   Mng:2304  
##  Mean   :61.28   Oth: 526  
##  3rd Qu.:71.00   Uni: 168  
##  Max.   :80.00   Unk: 152


OK, so the variables look great. Is it a spatial dataset that can be recognized by R? Not just yet. We know that X is likely some sort of longitude column and Y is likely some sort of latitude column, although they don’t exactly look right. We have to tell R that the X and Y coordinates are spatial data using the st_as_sf function in sf. With this command, we can specify which coordinates R should look at for longitude and latitude with the coords=c() function.


ca_pts <- st_as_sf(CAdata, coords=c("X","Y"))


Let’s check the coordinate reference system (CRS) using the st_crs command in the sf package.

st_crs(ca_pts)
## Coordinate Reference System: NA

Hmmm, NA. That still doesn’t look good. So how do we make this a spatial file? We will need to add a CRS.


Add coordinate reference system

Let’s add a CRS by using st_set_sf from the sf package. We get the CRS for this dataset from the MapGAM documentation (don’t worry–it took me forever to find this, but usually this is much easier to find). Then we will double check the CRS.

#Load the projection into an object called ca_proj

ca_proj <- "+proj=lcc +lat_1=40 +lat_2=41.66666666666666 
             +lat_0=39.33333333333334 +lon_0=-122 +x_0=2000000 
             +y_0=500000.0000000002 +ellps=GRS80 
             +datum=NAD83 +units=m +no_defs"

#Set CRS
ca_pts_crs <- st_set_crs(ca_pts, ca_proj)

#Look at dataset
summary(ca_pts_crs) 
##       time               event             AGE         INS      
##  Min.   : 0.004068   Min.   :0.0000   Min.   :25.00   Mcd: 431  
##  1st Qu.: 1.931247   1st Qu.:0.0000   1st Qu.:53.00   Mcr:1419  
##  Median : 4.749980   Median :1.0000   Median :62.00   Mng:2304  
##  Mean   : 6.496130   Mean   :0.6062   Mean   :61.28   Oth: 526  
##  3rd Qu.: 9.609031   3rd Qu.:1.0000   3rd Qu.:71.00   Uni: 168  
##  Max.   :24.997764   Max.   :1.0000   Max.   :80.00   Unk: 152  
##           geometry   
##  POINT        :5000  
##  epsg:NA      :   0  
##  +proj=lcc ...:   0  
##                      
##                      
## 
#Check the CRS
st_crs(ca_pts_crs)
## Coordinate Reference System:
##   User input: +proj=lcc +lat_1=40 +lat_2=41.66666666666666 
##              +lat_0=39.33333333333334 +lon_0=-122 +x_0=2000000 
##              +y_0=500000.0000000002 +ellps=GRS80 
##              +datum=NAD83 +units=m +no_defs 
##   wkt:
## PROJCRS["unknown",
##     BASEGEOGCRS["unknown",
##         DATUM["North American Datum 1983",
##             ELLIPSOID["GRS 1980",6378137,298.257222101,
##                 LENGTHUNIT["metre",1]],
##             ID["EPSG",6269]],
##         PRIMEM["Greenwich",0,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8901]]],
##     CONVERSION["unknown",
##         METHOD["Lambert Conic Conformal (2SP)",
##             ID["EPSG",9802]],
##         PARAMETER["Latitude of false origin",39.3333333333333,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8821]],
##         PARAMETER["Longitude of false origin",-122,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8822]],
##         PARAMETER["Latitude of 1st standard parallel",40,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8823]],
##         PARAMETER["Latitude of 2nd standard parallel",41.6666666666667,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8824]],
##         PARAMETER["Easting at false origin",2000000,
##             LENGTHUNIT["metre",1],
##             ID["EPSG",8826]],
##         PARAMETER["Northing at false origin",500000,
##             LENGTHUNIT["metre",1],
##             ID["EPSG",8827]]],
##     CS[Cartesian,2],
##         AXIS["(E)",east,
##             ORDER[1],
##             LENGTHUNIT["metre",1,
##                 ID["EPSG",9001]]],
##         AXIS["(N)",north,
##             ORDER[2],
##             LENGTHUNIT["metre",1,
##                 ID["EPSG",9001]]]]


tmap: Mapping vector data

Nice! We have a spatial dataset. That geometry column is how sf stores the geographic data, and latitude and longitude there are looking a bit more like we would expect. And we definitely have a full CRS with all sorts of info. OK, let’s plot our data to make sure they look spatial! We will use the tmap package to plot the points. We will first specify the tmap_mode of “view” which is interactive. There’s also a “plot” option which is nice for making exportable figures. We will then create an object called cancer_map and then add a layer with tm_shape(). This allows us to combine several maps into one, or to add layers on top of each other. Then we have to specify a level of that layer to display. Here we will use tm_dots() to to plot the points. In our options, we specify the size with size=.

tmap_mode("view")
## ℹ tmap mode set to "view".
cancer_map = tm_shape(ca_pts_crs) + tm_dots(size=0.5)
cancer_map


Let’s play around with some of the options. We can change the color with the col= option. We can make the dots smaller by specifying the size= option. And we can change the transparency of the points with the alpha= option.

cancer_map_small = tm_shape(ca_pts_crs) + tm_dots(col = "blue", size = 0.3, alpha = 0.5)
## 
## ── tmap v3 code detected ──────────────────────────────────
## [v3->v4] `tm_dots()`: use `fill_alpha` instead of `alpha`.
cancer_map_small


Let’s map the points color coded by the variable event, or whether or not the participant died over followup. We do that by specifying that the color (col=) is based on the column event. We have to specify that event is a categorical variable with style="cat".

cancer_map_events = tm_shape(ca_pts_crs) + tm_dots(size=0.3, col="event", style="cat")
## 
## ── tmap v3 code detected ──────────────────────────────────
## [v3->v4] `tm_dots()`: instead of `style = "cat"`, use
## fill.scale = `tm_scale_categorical()`.
cancer_map_events


Let’s see if we can change up the color scheme.

cancer_map_events_rg = tm_shape(ca_pts_crs) + tm_dots(col = "event", palette = c("0" = "gray", "1" = "red"), style="cat")
## 
## ── tmap v3 code detected ──────────────────────────────────
## [v3->v4] `tm_dots()`: instead of `style = "cat"`, use
## fill.scale = `tm_scale_categorical()`.
## ℹ Migrate the argument(s) 'palette' (rename to 'values')
##   to 'tm_scale_categorical(<HERE>)'
cancer_map_events_rg
## Multiple palettes called "gray" found: "matplotlib.gray", "tableau.gray". The first one, "matplotlib.gray", is returned.


Looking good! You earned yourself a tmap badge. Now get yourself a cookie.


tmap Badge
tmap Badge


Downloading Census Data

One of the primary sources of data that we’ll be using in this class is the United States Decennial Census and the American Community Survey. There are two ways to bring Census data into R: Using and API or downloading it from an online source.

Note that we will gather ACS data from all sources. Census boundaries changed in 2020, which means that 2016-2020 and later data will not completely merge with ACS data before 2020. So make sure you merge 2020 data only with 2020 data (but you can merge 2019 data with data between 2010-2019). This is especially important for tract data, with many new tracts created in 2020 and existing tracts experiencing dramatic changes in their boundaries between 2010 and 2020. See the impact of tract boundary changes between 2000 and 2010 here. You may also explore the Neighborhood Change Database which is available through the UC Davis library, and is a dataset that incorporates tract boundary changes over time. We are working on acquiring the 2020 data there!


Download Census data from an online source

The first way to obtain Census data is to download them directly from the web onto your hard drive. There are several websites where you can download Census data including Social Explorer and PolicyMap, which we have free access to as UC Davis affiliates, and the National Historical Geographic Information System (NHGIS), which is free for everyone. To find out how to download data from PolicyMap and NHGIS, check out tutorials here and here.


Use the Census API and tidycensus

The other way to bring Census data into R is to use the Census Application Program Interface (API). An API allows for direct requests for data in machine-readable form. That is, rather than you having to navigate to some website, scroll around to find a dataset, download that dataset once you find it, save that data onto your hard drive, and then bring the data into R, you just tell R to retrieve data directly from the source using one or two lines of code.

In order to directly download data from the Census API, you need a key. You can sign up for a free key here, which you should have already done before the lab. Type your key in quotes using the census_api_key() command.

census_api_key("YOUR API KEY GOES HERE", install = TRUE)


The option install = TRUE saves the API key in your R environment, which means you don’t have to run census_api_key() every single time. The function for downloading American Community Survey (ACS) Census data is get_acs(). The command for downloading decennial Census data is get_decennial(). Both functions come from the tidycensus package, which allows users to interface with the US Census Bureau’s decennial Census and American Community Survey APIs. Getting variables using the Census API requires knowing the variable ID - and there are thousands of variables (and thus thousands of IDs) across the different Census files. To rapidly search for variables, use the commands load_variables() and View(). Because we’ll be using the ACS in this guide, let’s check the variables in the most recent 2023 5-year ACS (2019-2023) using the following commands.

acs2023 <- load_variables(2023, "acs5", cache = TRUE)
View(acs2023)


A window should have popped up showing you a record layout of the 2019-2023 ACS. To search for specific data, select “Filter” located at the top left of this window and use the search boxes that pop up. For example, type in “Hispanic” in the box under “Label”. You should see near the top of the list the first set of variables we’ll want to download - race/ethnicity. Another way of finding variable names is to search them using Social Explorer. Click on the appropriate survey data year and then “American Community Survey Tables”, which will take you to a list of variables with their Census IDs.

Let’s extract race/ethnicity data and total population for California counties using the get_acs() command.


ca <- get_acs(geography = "county", 
              year = 2023,
              variables = c(tpopr = "B03002_001", 
                            nhwhite = "B03002_003", nhblk = "B03002_004", 
                            nhasn = "B03002_006", hisp = "B03002_012"), 
              state = "CA",
              survey = "acs5",
              output = "wide")
## Getting data from the 2019-2023 5-year ACS


In the above code, we specified the following arguments

  • geography: The level of geography we want the data in; in our case, the county. Other geographic options can be found here.
  • year: The end year of the data (because we want 2016-2020, we use 2020).
  • variables: The variables we want to bring in as specified in a vector you create using the function c(). Note that we created variable names of our own (e.g. “nhwhite”) and we put the ACS IDs in quotes (“B03002_003”). Had we not done this, the variable names will come in as they are named in the ACS, which are not very descriptive.
  • state: We can filter the counties to those in a specific state. Here it is “CA” for California. If we don’t specify this, we get all counties in the United States.
  • survey: The specific Census survey were extracting data from. We want data from the 5-year American Community Survey, so we specify “acs5”. The ACS comes in 1- and 5-year - varieties.
  • output: The argument tells R to return a wide dataset as opposed to a long dataset (see this vignette for more info).

Another useful option to set is cache_table = TRUE, so you don’t have to re-download after you’ve downloaded successfully the first time. Type in ? get_acs() to see the full list of options.


As you learned in Lab 1, whenever you bring in a dataset, the first thing you should always do is view it to get a sense of its structure and to make sure you got what you expected. One way of doing this is to use the glimpse() command

glimpse(ca)
## Rows: 58
## Columns: 12
## $ GEOID    <chr> "06001", "06003", "06005", "06007", "06009", "06011", "06013"…
## $ NAME     <chr> "Alameda County, California", "Alpine County, California", "A…
## $ tpoprE   <dbl> 1651949, 1695, 41029, 209470, 45995, 21895, 1161458, 27293, 1…
## $ tpoprM   <dbl> NA, 234, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ nhwhiteE <dbl> 466445, 993, 30234, 139527, 35599, 6869, 455961, 16668, 14254…
## $ nhwhiteM <dbl> 1170, 215, 341, 767, 318, 133, 1843, 199, 589, 979, 75, 554, …
## $ nhblkE   <dbl> 159042, 0, 781, 3550, 529, 311, 94864, 805, 1522, 42060, 158,…
## $ nhblkM   <dbl> 1736, 14, 143, 393, 140, 44, 1594, 123, 232, 1334, 126, 271, …
## $ nhasnE   <dbl> 528377, 8, 587, 11010, 1066, 101, 212373, 821, 9640, 108809, …
## $ nhasnM   <dbl> 2269, 8, 129, 497, 238, 159, 2008, 264, 416, 1332, 182, 403, …
## $ hispE    <dbl> 385245, 249, 6361, 40829, 6403, 13639, 316799, 5350, 27230, 5…
## $ hispM    <dbl> NA, 115, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …


You get a quick, compact summary of your tibble. You can also use the head() command, which shows you the first several rows of your data object (tail() will give you the last several rows).

head(ca)
## # A tibble: 6 × 12
##   GEOID NAME  tpoprE tpoprM nhwhiteE nhwhiteM nhblkE nhblkM nhasnE nhasnM  hispE
##   <chr> <chr>  <dbl>  <dbl>    <dbl>    <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
## 1 06001 Alam… 1.65e6     NA   466445     1170 159042   1736 528377   2269 385245
## 2 06003 Alpi… 1.69e3    234      993      215      0     14      8      8    249
## 3 06005 Amad… 4.10e4     NA    30234      341    781    143    587    129   6361
## 4 06007 Butt… 2.09e5     NA   139527      767   3550    393  11010    497  40829
## 5 06009 Cala… 4.60e4     NA    35599      318    529    140   1066    238   6403
## 6 06011 Colu… 2.19e4     NA     6869      133    311     44    101    159  13639
## # ℹ 1 more variable: hispM <dbl>


The tibble contains counties with their estimates for race/ethnicity. These variables end with the letter “E”. It also contains the margins of error for each estimate. These variables end with the letter “M”.

tidycensus is a game changer in being able to bring in Census data into R in a convenient, fast, efficient and tidy friendly way. We’ll be using this package in the next lab to bring in Census spatial data. And congratulations! You’ve just earned another badge. Fantastic!


tidycensus Badge
tidycensus Badge


Reading in data

PolicyMap

To save us time, I’ve uploaded a PolicyMap (link to .csv) on the Github for you to use in this lab. Save this file in the same folder where your Lab 2 R Markdown file resides. To read in a .csv file, first make sure that R is pointed to the folder you saved your data into. Type in getwd() to find out the current directory and setwd("directory name") to set the directory to the folder containing the data.

From a Mac laptop, I type in the following command to set the directory to the folder containing my data.

setwd("/Users/pjames1/Dropbox/UC Davis Folders/SPH 215 GIS and Public Health/Github_Website/SPH215/")


For a Windows system, you can find the pathway of a file by right clicking on it and selecting Properties. You will find that instead of a forward slash like in a Mac, a windows pathway will be indicated by a single back slash . R doesn’t like this because it thinks of a single back slash as an escape character. Use instead two back slashes \

setwd("C:\\Users\\pjames\\Documents\\UCD\\Spring2025\\SPH215\\Labs\\Lab 2")

or a forward slash /.

setwd("C:/Users/pjames/Documents/UCD/Spring2025/SPH215/Labs/Lab 2")

You can also manually set the working directory by clicking on Session -> Set Working Directory -> Choose Directory from the menu.


Once you’ve set your directory, use the function read_csv(), which is a part of the tidyverse package, and plug in the name of the file in quotes inside the parentheses. Make sure you include the .csv extension.

ca.pm <- read_csv("PolicyMap Data 2025-03-27 192555 UTC.csv", skip = 1)
## Rows: 58 Columns: 10
## ── Column specification ───────────────────────────────────
## Delimiter: ","
## chr (8): GeoID_Description, GeoID_Name, SitsinState, GeoID, GeoID_Formatted,...
## dbl (2): mhhinc, GeoVintage
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.


The option skip = 1 tells R to skip the first row of the file when bringing it in. This is done because there are two rows of column names. The first row contains the extended version, while the second is the abridged version. Above we keep the abridged version.

You should see a tibble ca.pm pop up in your Environment window (top right). What does our data set look like?

glimpse(ca.pm)
## Rows: 58
## Columns: 10
## $ GeoID_Description <chr> "County", "County", "County", "County", "County", "C…
## $ GeoID_Name        <chr> "Alameda", "Alpine", "Amador", "Butte", "Calaveras",…
## $ SitsinState       <chr> "CA", "CA", "CA", "CA", "CA", "CA", "CA", "CA", "CA"…
## $ GeoID             <chr> "06001", "06003", "06005", "06007", "06009", "06011"…
## $ GeoID_Formatted   <chr> "=\"06001\"", "=\"06003\"", "=\"06005\"", "=\"06007\…
## $ mhhinc            <dbl> 126240, 110781, 81526, 68574, 79877, 75149, 125727, …
## $ TimeFrame         <chr> "2019-2023", "2019-2023", "2019-2023", "2019-2023", …
## $ GeoVintage        <dbl> 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022…
## $ Source            <chr> "Census", "Census", "Census", "Census", "Census", "C…
## $ Location          <chr> "California (State)", "California (State)", "Califor…


If you like viewing your data through an Excel style worksheet, type in View(ca.pm), and ca.pm should pop up in the top left window of your R Studio interface.


More data wrangling

We learned about the various data wrangling related functions from the tidyverse package in Lab 1. Let’s employ some of those functions to create a single county level dataset that joins the datasets we downloaded from the Census API and PolicyMap.

We are going to combine these datasets using the county FIPS codes. In the Census API and PolicyMap, these are contained in the variables GEOID and GeoID, respectively. Let’s make sure they are in the same class.

class(ca.pm$GeoID)
## [1] "character"
class(ca$GEOID)
## [1] "character"


Piping

One of the important innovations from the tidyverse is the pipe operator %>%. You use the pipe operator when you want to combine multiple operations into one line of continuous code. Let’s create our final data object cacounty using our brand new friend the pipe. Let’s put together a lot of what we’ve done over the past few weeks into one step! Now we are cooking with gas!

cacounty <- ca %>% 
      left_join(ca.pm, by = c("GEOID" = "GeoID")) %>%
      mutate(pwhite = nhwhiteE/tpoprE, pasian = nhasnE/tpoprE, 
              pblack = nhblkE/tpoprE, phisp = hispE/tpoprE,
             mhisp = case_when(phisp > 0.5 ~ "Majority",
                               TRUE ~ "Not Majority")) %>%
      rename(County = GeoID_Name) %>%
      select(GEOID, County, pwhite, pasian, pblack, phisp, mhisp, mhhinc)
glimpse(cacounty)
## Rows: 58
## Columns: 8
## $ GEOID  <chr> "06001", "06003", "06005", "06007", "06009", "06011", "06013", …
## $ County <chr> "Alameda", "Alpine", "Amador", "Butte", "Calaveras", "Colusa", …
## $ pwhite <dbl> 0.28236041, 0.58584071, 0.73689342, 0.66609538, 0.77397543, 0.3…
## $ pasian <dbl> 0.319850673, 0.004719764, 0.014306954, 0.052561226, 0.023176432…
## $ pblack <dbl> 0.0962753693, 0.0000000000, 0.0190353165, 0.0169475343, 0.01150…
## $ phisp  <dbl> 0.2332064, 0.1469027, 0.1550367, 0.1949157, 0.1392108, 0.622927…
## $ mhisp  <chr> "Not Majority", "Not Majority", "Not Majority", "Not Majority",…
## $ mhhinc <dbl> 126240, 110781, 81526, 68574, 79877, 75149, 125727, 66780, 1061…

Let’s break down what the pipe is doing here. First, you start out with your dataset ca. You “pipe” that into the command left_join(). Notice that you didn’t have to type in ca inside that command - %>% pipes that in for you. The command joins the data object ca.pm to ca. The result of this function gets piped into the mutate() function, which creates the percent race/ethnicity (from the Census API), and majority Hispanic variables. This gets piped into the rename() function, which renames the ambiguous variable name GeoID_Name to the more descriptive name County. This then gets piped into the final function, select(), which keeps the necessary variables. Finally, the code saves the result into cacounty which we designated at the beginning with the arrow operator.

Piping makes code clearer, and simultaneously gets rid of the need to define any intermediate objects that you would have needed to keep track of while reading the code. PIPE, Pipe, and pipe whenever you can. We need some stinkin badges!


pipe Badge!
pipe Badge!


Saving data

If you want to save your data frame or tibble as a csv file on your hard drive, use the command write_csv(). Before you save a file, make sure R is pointed to the appropriate folder on your hard drive by using the function getwd(). If it’s not pointed to the right folder, use the function setwd() to set the appropriate working directory.

write_csv(cacounty, "lab2_file.csv")

The first argument is the name of the R object you want to save. The second argument is the name of the csv file in quotes. Make sure to add the .csv extension. The file is saved in your current working directory.


Exploratory data analysis

The functions above help us bring in and clean data. The next set of functions covered in this section will help us summarize the data. Data refer to pieces of information that describe a status or a measure of magnitude. A variable is a set of observations on a particular characteristic. The distribution of a variable is a listing showing all the possible values of the data for that variable and how often they occur. Exploratory Data Analysis (EDA) encompasses a set of methods (some would say a framework or perspective) for summarizing a variable’s distribution, and the relationship between the distributions of two or more variables. We will cover two general approaches to summarizing your data: descriptive statistics and visualization via graphs and charts.


Descriptive statistics

When describing a distribution, your quantitative message is often best communicated by reducing data to a few summary numbers. These numbers are meant to summarize the “typical” value in the distribution (e.g., mean, median, mode) and the variation or “spread” in the distribution (e.g., minimum/maximum, interquartile range, standard deviation). These summary numbers are known as descriptive statistics.

We can use the function summarize() to get descriptive statistics of our data. For example, let’s calculate the mean household income in California counties. The first argument inside summarize() is the data object cacounty and the second argument is the function calculating the specific summary statistic, in this case mean().

cacounty %>%
  summarize(Mean = mean(mhhinc))
## # A tibble: 1 × 1
##     Mean
##    <dbl>
## 1 87001.

The average county median household income is $87,001. If the variable mhhinc contained missing values, we would have gotten NA as a result. To omit missing values from the calculation, you need to add rm = TRUE to mean().

We can calculate more than one summary statistic within summarize(). What is the spread of the distribution? We can add to summarize() the function sd() to calculate the standard deviation.

cacounty %>%
  summarize(Mean = mean(mhhinc), SD = sd(mhhinc))
## # A tibble: 1 × 2
##     Mean     SD
##    <dbl>  <dbl>
## 1 87001. 25547.


Does the average income differ by California region? First, let’s create a new variable region designating each county as Bay Area, Southern California, Central Valley, Capital Region and the Rest of California using the case_when() function within the mutate() function.

cacounty <- cacounty %>%
    mutate(region = case_when(County == "Sonoma" | County == "Napa" | 
                              County == "Solano" | County == "Marin" | 
                              County == "Contra Costa" | County == "San Francisco" |
                              County == "San Mateo" | County == "Alameda" | 
                              County == "Santa Clara" ~ "Bay Area",
                              County == "Imperial" | County == "Los Angeles" | 
                              County == "Orange" | County == "Riverside" |
                              County == "San Diego" | County == "San Bernardino" |
                              County == "Ventura" ~ "Southern California",
                              County == "Fresno" | County == "Madera" | 
                              County == "Mariposa" | County == "Merced" | 
                              County == "Tulare" | 
                              County == "Kings" ~ "Central Valley",
                              County == "Alpine" | County == "Colusa" |
                              County == "El Dorado" | County == "Glenn" |
                              County == "Placer" | County == "Sacramento" |
                              County == "Sutter" | County == "Yolo" |
                              County == "Yuba" ~ "Capital Region",
                              TRUE ~ "Rest"))


Next, we need to pair summarize() with the function group_by(). The function group_by() tells R to run subsequent functions on the data object by a group characteristic (such as gender, educational attainment, or in this case, region). We’ll need to use our new best friend %>% to accomplish this task.

cacounty %>%
  group_by(region) %>%
  summarize(Mean = mean(mhhinc))
## # A tibble: 5 × 2
##   region                 Mean
##   <chr>                 <dbl>
## 1 Bay Area            129297.
## 2 Capital Region       89288.
## 3 Central Valley       69265.
## 4 Rest                 74959.
## 5 Southern California  91332.

The first pipe sends cacounty into the function group_by(), which tells R to group cacounty by the variable region.

How do you know the tibble is grouped? Because it tells you!

cacounty %>%
  group_by(region) 
## # A tibble: 58 × 9
## # Groups:   region [5]
##    GEOID County       pwhite  pasian  pblack phisp mhisp        mhhinc region   
##    <chr> <chr>         <dbl>   <dbl>   <dbl> <dbl> <chr>         <dbl> <chr>    
##  1 06001 Alameda       0.282 0.320   0.0963  0.233 Not Majority 126240 Bay Area 
##  2 06003 Alpine        0.586 0.00472 0       0.147 Not Majority 110781 Capital …
##  3 06005 Amador        0.737 0.0143  0.0190  0.155 Not Majority  81526 Rest     
##  4 06007 Butte         0.666 0.0526  0.0169  0.195 Not Majority  68574 Rest     
##  5 06009 Calaveras     0.774 0.0232  0.0115  0.139 Not Majority  79877 Rest     
##  6 06011 Colusa        0.314 0.00461 0.0142  0.623 Majority      75149 Capital …
##  7 06013 Contra Costa  0.393 0.183   0.0817  0.273 Not Majority 125727 Bay Area 
##  8 06015 Del Norte     0.611 0.0301  0.0295  0.196 Not Majority  66780 Rest     
##  9 06017 El Dorado     0.741 0.0501  0.00791 0.142 Not Majority 106190 Capital …
## 10 06019 Fresno        0.270 0.108   0.0416  0.541 Majority      71434 Central …
## # ℹ 48 more rows


The second pipe takes this grouped dataset and sends it into the summarize() command, which calculates the mean income (by region, because the dataset is grouped by region).

To get the mean, median and standard deviation of median income, its correlation with percent Hispanic, and give column labels for the variables in the resulting summary table, we type in:

cacounty %>%
  group_by(region) %>%
  summarize(Mean = mean(mhhinc),
            Median = median(mhhinc),
            SD = sd(mhhinc),
            Correlation = cor(mhhinc, phisp))
## # A tibble: 5 × 5
##   region                 Mean  Median     SD Correlation
##   <chr>                 <dbl>   <dbl>  <dbl>       <dbl>
## 1 Bay Area            129297. 126240  22246.      -0.620
## 2 Capital Region       89288.  88724  17295.      -0.788
## 3 Central Valley       69265.  69120.  3918.       0.423
## 4 Rest                 74959.  71931  15803.       0.606
## 5 Southern California  91332.  89672  19132.      -0.951


The variable mhhinc is numeric. How do we summarize categorical variables? We usually summarize categorical variables by examining a frequency table. To get the percent of counties that have a majority Hispanic population mhisp, you’ll need to combine the functions group_by(), summarize() and mutate() using %>%.

cacounty %>%
  group_by(mhisp) %>%
  summarize(n = n()) %>%
  mutate(freq = n / sum(n))
## # A tibble: 2 × 3
##   mhisp            n  freq
##   <chr>        <int> <dbl>
## 1 Majority        12 0.207
## 2 Not Majority    46 0.793


The code group_by(mhisp) separates the counties by the categories of mhisp (Majority, Not Majority). We then used summarize() to count the number of counties that are Majority and Not Majority. The function to get a count is n(), and we saved this count in a variable named n. Next, we used mutate() on this table to get the proportion of counties by Majority Hispanic designation. The code sum(n) adds the values of n. We then divide the value of each n by this sum. That yields the final frequency table.

Instead of calculating descriptive statistics one at a time using summarize(), you can obtain a set of summary statistics for one or all the numeric variables in your dataset using the summary() function.

summary(cacounty)
##     GEOID              County              pwhite            pasian       
##  Length:58          Length:58          Min.   :0.09421   Min.   :0.00000  
##  Class :character   Class :character   1st Qu.:0.31535   1st Qu.:0.01888  
##  Mode  :character   Mode  :character   Median :0.49018   Median :0.04404  
##                                        Mean   :0.50723   Mean   :0.07748  
##                                        3rd Qu.:0.66599   3rd Qu.:0.08630  
##                                        Max.   :0.88832   Max.   :0.39306  
##      pblack             phisp            mhisp               mhhinc      
##  Min.   :0.000000   Min.   :0.05892   Length:58          Min.   : 53498  
##  1st Qu.:0.009634   1st Qu.:0.15611   Class :character   1st Qu.: 67888  
##  Median :0.016844   Median :0.27110   Mode  :character   Median : 80702  
##  Mean   :0.028112   Mean   :0.32170                      Mean   : 87001  
##  3rd Qu.:0.033734   3rd Qu.:0.46747                      3rd Qu.:102701  
##  Max.   :0.125967   Max.   :0.85579                      Max.   :159674  
##     region         
##  Length:58         
##  Class :character  
##  Mode  :character  
##                    
##                    
## 


Tables for presentation

The output from the descriptive statistics we’ve ran so far is not presentation ready. For example, taking a screenshot of the following results table produces unnecessary information that is confusing and messy.

cacounty %>%
  group_by(region) %>%
  summarize(Mean = mean(mhhinc),
            Median = median(mhhinc),
            SD = sd(mhhinc),
            Correlation = cor(mhhinc, phisp))
## # A tibble: 5 × 5
##   region                 Mean  Median     SD Correlation
##   <chr>                 <dbl>   <dbl>  <dbl>       <dbl>
## 1 Bay Area            129297. 126240  22246.      -0.620
## 2 Capital Region       89288.  88724  17295.      -0.788
## 3 Central Valley       69265.  69120.  3918.       0.423
## 4 Rest                 74959.  71931  15803.       0.606
## 5 Southern California  91332.  89672  19132.      -0.951

Furthermore, you would like to show a table, say, in a manuscript that does not require you to take a screenshot or copying and pasting into Excel, but instead can be produced via code, that way it can be fixed if there is an issue, and is reproducible.

One way of producing presentation tables in R is through the flextable package. First, you will need to save the tibble or data frame of results into an object. For example, let’s save the above results into an object named region.summary

region.summary <- cacounty %>%
  group_by(region) %>%
  summarize(Mean = mean(mhhinc),
            Median = median(mhhinc),
            SD = sd(mhhinc),
            Correlation = cor(mhhinc, phisp))

You then input the object into the function flextable(). Save it into an object called my_table

my_table <- flextable(region.summary)
my_table

region

Mean

Median

SD

Correlation

Bay Area

129,297.33

126,240.0

22,246.495

-0.6196837

Capital Region

89,287.78

88,724.0

17,294.860

-0.7878129

Central Valley

69,265.17

69,119.5

3,918.442

0.4231022

Rest

74,958.74

71,931.0

15,803.202

0.6064605

Southern California

91,331.86

89,672.0

19,131.821

-0.9513650


You should see a relatively clean table pop up either in your console or Viewer window.


What kind of object is my_table?

class(my_table)
## [1] "flextable"


After doing this, we can progressively pipe the my_table object through more flextable formatting functions. For example, you can change the column header names using the function set_header_labels() and center the header names using the function align().

my_table <- my_table %>%
          set_header_labels(
            region = "Region",
            Mean = "Mean",
            Median = "Median",
            SD = "Standard Deviation",
            Correlation = "Correlation") %>%
              flextable::align(align = "center", part = "all")

my_table

Region

Mean

Median

Standard Deviation

Correlation

Bay Area

129,297.33

126,240.0

22,246.495

-0.6196837

Capital Region

89,287.78

88,724.0

17,294.860

-0.7878129

Central Valley

69,265.17

69,119.5

3,918.442

0.4231022

Rest

74,958.74

71,931.0

15,803.202

0.6064605

Southern California

91,331.86

89,672.0

19,131.821

-0.9513650

Well doesn’t that look spiffy! There are a slew of options for formatting your table, including adding footnotes, borders, shade and other features. Check out this useful tutorial for an explanation of some of these features.

Once you’re done formatting your table, you can then export it to Word, PowerPoint or HTML or as an image (PNG) files. To do this, use one of the following functions: save_as_docx(), save_as_pptx(), save_as_image(), and save_as_html().

Use the save_as_image() function to save your table as an image.

save_as_image(my_table, path = "reg_income.png")
## [1] "reg_income.png"

You first put in the table my_table, and set the file name with the .png extension. Check your working directory. You should see the file reg_income.png.


Data visualization

Another way of summarizing variables and their relationships is through graphs and charts. The main package for R graphing is ggplot2 which is a part of the tidyverse package. The graphing function is ggplot() and it takes on the basic template

ggplot(data = <DATA>) +
      <GEOM_FUNCTION>(mapping = aes(x, y)) +
      <OPTIONS>()


  1. ggplot() is the base function where you specify your dataset using the data = argument.
  2. You then need to build on this base by using the plus operator + and () where () is a unique geom function indicating the type of graph you want to plot. Each unique function has its unique set of mapping arguments which you specify using the mapping = aes() argument. Charts and graphs have an x-axis, y-axis, or both. Check this ggplot cheat sheet for all possible geoms.
  3. <OPTIONS>() are a set of functions you can specify to change the look of the graph, for example relabeling the axes or adding a title.

The basic idea is that a ggplot graphic layers geometric objects (circles, lines, etc), themes, and scales on top of data.

You first start out with the base layer. It represents the empty ggplot layer defined by the ggplot() function.

cacounty %>%
  ggplot()

We get an empty plot. We haven’t told ggplot() what type of geometric object(s) we want to plot, nor how the variables should be mapped to the geometric objects, so we just have a blank plot. We have geoms to paint the blank canvas.

From here, we add a “geom” layer to the ggplot object. Layers are added to ggplot objects using +, instead of %>%, since you are not explicitly piping an object into each subsequent layer, but adding layers on top of one another. Each geom is associated with a specific type of graph.

Let’s go through some of the more common and popular graphs for visualizing your data.


Histogram

A typical visual for summarizing a single numeric variable is a histogram. To create a histogram, use geom_histogram() for <GEOM_FUNCTION()>. Let’s create a histogram of median household income. Note that we don’t need to specify the y= here because we are plotting only one variable. We pipe in the object cacounty into ggplot() to establish the base layer. We then use geom_histogram() to add the data layer on top of the base.

cacounty %>%
  ggplot() + 
  geom_histogram(mapping = aes(x=mhhinc)) 
## `stat_bin()` using `bins = 30`. Pick better value with
## `binwidth`.

We can continue to add layers to the plot. For example, we use the argument xlab("Median household income") to label the x-axis as “Median household income”.

cacounty %>%
  ggplot() + 
  geom_histogram(mapping = aes(x=mhhinc)) +
  xlab("Median household income")
## `stat_bin()` using `bins = 30`. Pick better value with
## `binwidth`.

Note the message produced with the plot. It tells us that we can use the bins = argument to change the number of bins used to produce the histogram. You can increase the number of bins to make the bins narrower and thus get a finer grain of detail. Or you can decrease the number of bins to get a broader visual summary of the shape of the variable’s distribution. Compare bins = 10 to bins = 50.


cacounty %>%
  ggplot() + 
  geom_histogram(mapping = aes(x=mhhinc), bins=10) +
  xlab("Median household income")


Boxplot

We can use a boxplot to visually summarize the distribution of a single numeric variable or the relationship between a categorical and numeric variable. Use geom_boxplot() for <GEOM_FUNCTION()> to create a boxplot. Let’s examine median household income. Note that a boxplot uses y= rather than x= to specify where mhhinc goes. We also provide a descriptive y-axis label using the ylab() function.

cacounty %>%
  ggplot() +
    geom_boxplot(mapping = aes(y = mhhinc)) +
    ylab("Median household income")


Let’s examine the distribution of median income by mhisp. Because we are examining the association between two variables, we need to specify x and y variables in aes() (we also specify both x- and y-axis labels).

cacounty %>%
  ggplot() +
    geom_boxplot(mapping = aes(x = mhisp, y = mhhinc)) +
    xlab("Majority Hispanic") +
    ylab("Median household income")

The top and bottom of a boxplot represent the 75th and 25th percentiles, respectively. The line in the middle of the box is the 50th percentile. Points outside the whiskers represent outliers. Outliers are defined as having values that are either larger than the 75th percentile plus 1.5 times the IQR or smaller than the 25th percentile minus 1.5 times the IQR.


The boxplot is for all counties combined. Use the facet_wrap() function to separate by region. Notice the tilde ~ before the variable region inside facet_wrap().

cacounty %>%
  ggplot() +
    geom_boxplot(mapping = aes(x = mhisp, y = mhhinc)) +
    xlab("Majority Hispanic") +
    ylab("Median household income") +
    facet_wrap(~region) 


Bar chart

The primary purpose of a bar chart is to illustrate and compare the values for a categorical variable. Bar charts show either the number or frequency of each category. To create a bar chart, use geom_bar() for (). Let’s show a bar chart of median household income by region. We’ll borrow from code above that generated a tibble of mean household income by region, and pipe that into ggplot().

cacounty %>%
  group_by(region) %>%
  summarize(Mean = mean(mhhinc)) %>%
  ggplot(aes(x=region, y = Mean)) +
  geom_bar(stat = "Identity") +
  xlab("Region") +
  ylab("Average household income")


Right now the bars are ordered based on the region names. We can order the bars in descending order based on household income by using the reorder() function. Notice the negative sign in front of Mean to order by descending order.

cacounty %>%
  group_by(region) %>%
  summarize(Mean = mean(mhhinc)) %>%
  ggplot(aes(x=reorder(region, -Mean), y = Mean)) +
  geom_bar(stat = "Identity") +
  xlab("Region") +
  ylab("Average household income")


We can flip the axes using the function coord_flip().

cacounty %>%
  group_by(region) %>%
  summarize(Mean = mean(mhhinc)) %>%
  ggplot(aes(x=reorder(region, -Mean), y = Mean)) +
  geom_bar(stat = "Identity") +
  xlab("Region") +
  ylab("Average household income") +
  coord_flip()


Well aren’t we fancy? ggplot() is a powerful function, and you can make a lot of visually captivating graphs. We have just scratched the surface of its functions and features. You can also make your graphs really “pretty” and professional looking by altering graphing features, including colors, labels, titles and axes. For a list of ggplot() functions that alter various features of a graph, check out Chapter 28 in RDS.

Here’s your ggplot2 badge. Wear it with pride! OK, we’ve done a lot today. Get outside and get some fresh air!


ggplot2 Badge
ggplot2 Badge


Other US Government datasets

Check out the Data Sources link for more links to US Government Data


Acknowledgements

Major acknowledgements to Noli Brazil (as always) and Crime by the Numbers.

LS0tCnRpdGxlOiAiTGFiIDI6IEdlb2NvZGluZywgVmVjdG9yIERhdGEsIGFuZCBDZW5zdXMgRGF0YSIKLS0tCgpJbiB0aGlzIGxhYiwgd2UgYXJlIGdvaW5nIHRvIHdvcmsgd2l0aCB2ZWN0b3IgZGF0YSwgd2hpY2ggd2UndmUgdGFsa2VkIGFib3V0IGxhc3Qgd2Vlay4gV2UgYXJlIGdvaW5nIHRvIHdvcmsgd2l0aCBhIGRhdGFzZXQgb2YgY2FuY2VyIHBhdGllbnRzIGFjcm9zcyBDYWxpZm9ybmlhLCBhcyB3ZWxsIGFzIENlbnN1cyBkYXRhIG9uIHNvY2lvZWNvbm9taWMgZmFjdG9ycy4gV2Ugd2lsbCBhbHNvIHRhbGsgYWJvdXQgZ2VvY29kaW5nIGFkZHJlc3NlcyBhbmQgd2lsbCBkaXNjdXNzIGhvdyB0byB2aXN1YWxpemUgZGF0YS4gCgpUaGUgb2JqZWN0aXZlcyBvZiB0aGlzIGd1aWRlIGFyZSB0byB0ZWFjaCB5b3U6CgoxLiBIb3cgdG8gZ2VvY29kZSBhZGRyZXNzZXMKMi4gSG93IHRvIGJyaW5nIGluIGFuZCB2aXN1YWxpemUgcG9pbnQgZGF0YQozLiBIb3cgdG8gZG93bmxvYWQgQ2Vuc3VzIGRhdGEgdXNpbmcgdGhlIENlbnN1cyBBUEkKNC4gSG93IHRvIGNvbmR1Y3QgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcwoKTGV0J3MgZ2V0IGNyYWNraW5nIQoKXAoKIyBPcGVuIHVwIGFuIFIgTWFya2Rvd24gZmlsZQoKV2UgaG9wZWZ1bGx5IHJlbWVtYmVyIHNvbWUgb2YgdGhpcyBmcm9tIGxhc3Qgd2VlayBpbiBbTGFiIDFdKGxhYjEucm1kKSwgYnV0IGxldCdzIG9wZW4gYW4gUiBNYXJrZG93biBmaWxlIGJ5IGNsaWNraW5nIG9uICpGaWxlKiBhdCB0aGUgdG9wIG1lbnUgaW4gUlN0dWRpbywgc2VsZWN0ICpOZXcgRmlsZSosIGFuZCB0aGVuICpSIE1hcmtkb3duLi4uKi4gQSB3aW5kb3cgc2hvdWxkIHBvcCB1cC4gSW4gdGhhdCB3aW5kb3csIGZvciAqdGl0bGUqLCBwdXQgaW4g4oCcTGFiIDLigJ0uIEZvciAqYXV0aG9yKiwgcHV0IHlvdXIgbmFtZS4gTGVhdmUgdGhlIEhUTUwgcmFkaW8gYnV0dG9uIGNsaWNrZWQsIGFuZCBzZWxlY3QgT0suIEEgbmV3IFIgTWFya2Rvd24gZmlsZSBzaG91bGQgcG9wIHVwIGluIHRoZSB0b3AgbGVmdCB3aW5kb3cuCgpcCgojIFdoYXQgcGFja2FnZXMgZG8gd2UgbmVlZD8KCkxldCdzIGxvYWQgc29tZSBwYWNrYWdlcyB0aGF0IHdlIHdpbGwgbmVlZCB0aGlzIHdlZWsuIFdlIG5lZWQgdG8gbG9hZCBhbnkgcGFja2FnZXMgd2UgcHJldmlvdXNseSBpbnN0YWxsZWQgIHVzaW5nIHRoZSBmdW5jdGlvbiBgbGlicmFyeSgpYC4gUmVtZW1iZXIsIGluc3RhbGwgb25jZSwgbG9hZCBldmVyeSB0aW1lLiBBbmQgaWYgaXQgZ2l2ZXMgeW91IGFuIGVycm9yIGZvciBgbm8gcGFja2FnZSBjYWxsZWQuLi5gLCB0aGVuIHdlIG5lZWQgdG8gaW5zdGFsbCB0aG9zZSBwYWNrYWdlcyB1c2luZyBgaW5zdGFsbC5wYWNrYWdlcygpYC4gU28gd2hlbiB1c2luZyBhIHBhY2thZ2UsIGBsaWJyYXJ5KClgIHNob3VsZCBhbHdheXMgYmUgYXQgdGhlIHRvcCBvZiB5b3VyIFIgTWFya2Rvd24uCgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkodGlkeWdlb2NvZGVyKQpsaWJyYXJ5KHNmKQpsaWJyYXJ5KE1hcEdBTSkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGlkeWNlbnN1cykKbGlicmFyeShmbGV4dGFibGUpCmxpYnJhcnkodG1hcCkKYGBgCgpcCgojIEdlb2NvZGluZwoKRmlyc3QsIHdlIGFyZSBnb2luZyB0byB0YWNrbGUgaG93IHdlIHRha2UgYWRkcmVzc2VzIGFuZCBjb252ZXJ0IHRoZW0gdG8gc3BhdGlhbCBkYXRhIChsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlKS4gU28sIGxldCdzIHNheSB3ZSB3YW50ZWQgdG8gbWFwIGFsbCBvZiB0aGUgbWFyaWp1YW5hIGRpc3BlbnNhcmllcyBhY3Jvc3MgU2FuIEZyYW5jaXNjby4gTGV0J3MgZG93bmxvYWQgYSAuY3N2IG9mIHRoZXNlIGFkZHJlc3NlcyBmcm9tIHRoZSBHaXRodWIgc2l0ZSwgdGhlbiB0YWtlIGEgbG9vayBhdCB0aGUgZGF0YXNldC4KCmBgYHtyfQpkb3dubG9hZC5maWxlKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcGphbWVzLXVjZGF2aXMvU1BIMjE1L3JlZnMvaGVhZHMvbWFpbi9zYW5fZnJhbmNpc2NvX2FjdGl2ZV9tYXJpanVhbmFfcmV0YWlsZXJzLmNzdiIsICJzYW5fZnJhbmNpc2NvX2FjdGl2ZV9tYXJpanVhbmFfcmV0YWlsZXJzLmNzdiIsIG1vZGUgPSAid2IiKQoKc2ZfbWogPC0gcmVhZF9jc3YoInNhbl9mcmFuY2lzY29fYWN0aXZlX21hcmlqdWFuYV9yZXRhaWxlcnMuY3N2IikKCmhlYWQoc2ZfbWopICAgICAgICAgICAgICAgICAgCgpgYGAKClwKCk9LLCBzb21lIGludGVyZXN0aW5nIGNvbHVtbnMgdGhlcmUsIGFuZCB3ZSBoYXZlICpQcmVtaXNlIEFkZHJlc3MqIGFzIGEgY29sdW1uIHRoYXQgd2UgbWlnaHQgd2FudCB0byBtYWtlIHNwYXRpYWwuIExldCdzIGxvb2sgY2xvc2VyIGF0IHRoYXQuCgoKYGBge3J9CmhlYWQoc2ZfbWokYFByZW1pc2UgQWRkcmVzc2ApCmBgYAoKXAoKT0sgdGhhdCBjb2x1bW4gbG9va3MgbGlrZSB3aGF0IHdlIHdhbnQgdG8gZ2VvY29kZS4gQnV0IGhvdyBkbyB3ZSB0YWtlIHRoZXNlIGFkZHJlc3NlcyBhbmQgbWFrZSB0aGVtIGludG8gc3BhdGlhbCBpbmZvcm1hdGlvbj8gV2UgaGF2ZSB0byBnZW9jb2RlIHRoZW0hIFRvIGRvIHNvLCB3ZSB3aWxsIHVzZSB0aGUgKip0aWR5Z2VvY29kZXIqKiBwYWNrYWdlIGluIFIuIEJ1dCBmaXJzdCwgd2Ugc2VlIHRoYXQgdGhlIGFkZHJlc3NlcyBsb29rIGEgbGl0dGxlIHN0cmFuZ2UuIFRoZSBhZGRyZXNzIGNvdW50eSBpcyBhbHdheXMgIkNvdW50eTogU0FOIEZSQU5DSVNDTyIgc28gd2Ugd2lsbCBgZ3N1YigpYCBvdXQgdGhhdCBlbnRpcmUgc3RyaW5nLgpgYGB7cn0Kc2ZfbWokYFByZW1pc2UgQWRkcmVzc2AgPC0gZ3N1YigiIENvdW50eTogU0FOIEZSQU5DSVNDTyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiIiwgc2ZfbWokYFByZW1pc2UgQWRkcmVzc2ApCmhlYWQoc2ZfbWokYFByZW1pc2UgQWRkcmVzc2ApCmBgYApUaGF0IGxvb2tzIG11Y2ggYmV0dGVyLgoKXAoKTm93IGxldCdzIGdpdmUgYSB0cnkgdG8gZ2VvY29kaW5nIHRoZXNlIGFkZHJlc3NlcyB3aXRoIHRoZSAqKnRpZHlnZW9jb2RlcioqIHBhY2thZ2UuIFdlIHdpbGwgdXNlIHRoZSBgZ2VvY29kZSgpYCBmdW5jdGlvbiB0byBhZGQgYSBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIHRvIGVhY2ggb2Ygb3VyIGFkZHJlc3NlcyBpbiB0aGUgKlByZW1pc2UgQWRkcmVzcyogY29sdW1uLiBXZSB3aWxsIHVzZSB0aGUgT3BlbiBTdHJlZXQgTWFwIGFkZHJlc3MgZGF0YWJhc2UgYnkgc3BlY2lmeWluZyBgbWV0aG9kID0gIm9zbSJgLiBUaGlzIHdpbGwgdGFrZSBhYm91dCBhIG1pbnV0ZSB0byBydW4sIHNvIGJlIHBhdGllbnQhIApgYGB7cn0Kc2ZfbWpfZ2VvICAgICAgPC0gZ2VvY29kZShzZl9taiwgIlByZW1pc2UgQWRkcmVzcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gIm9zbSIpCmhlYWQoc2ZfbWpfZ2VvKQpgYGAKClwKCkhtbSwgbG9va3MgbGlrZSBzb21lIG9mIG91ciBhZGRyZXNzZXMgaGF2ZSBhbiBgTkFgIGZvciB0aGVpciBsYXQgYW5kIGxvbmcuIExldCdzIHRha2UgYSBjbG9zZXIgbG9vay4KCmBgYHtyfQpzdW1tYXJ5KHNmX21qX2dlbyRsYXQpCnN1bW1hcnkoc2ZfbWpfZ2VvJGxvbmcpCmBgYAoKXAoKTG9va3MgbGlrZSB3ZSBoYXZlIDEwIGFkZHJlc3NlcyBtaXNzaW5nICpsYXQqIGFuZCAxMCBtaXNzaW5nICpsb25nKi4gTGV0J3MgdHJ5IHRoaXMgYWdhaW4gdXNpbmcgYSBkaWZmZXJlbnQgZ2VvY29kaW5nIGRhdGFiYXNlIGNhbGxlZCAqYXJjZ2lzKi4KCmBgYHtyfQpzZl9tal9nZW9fYXJjICAgICAgPC0gZ2VvY29kZShzZl9taiwgIlByZW1pc2UgQWRkcmVzcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImFyY2dpcyIpCmhlYWQoc2ZfbWpfZ2VvX2FyYykKc3VtbWFyeShzZl9tal9nZW9fYXJjJGxhdCkKc3VtbWFyeShzZl9tal9nZW9fYXJjJGxvbmcpCmBgYAoKXAoKV29vaG9vISBObyBtaXNzaW5nbmVzcy4gTG92ZSB0byBzZWUgaXQuIE9LLCBsZXQncyBwbG90IHRoZXNlIGRhdGEgYW5kIHNlZSBob3cgdGhleSBsb29rLiAKCmBgYHtyfQpwbG90KHNmX21qX2dlb19hcmMkbG9uZywgc2ZfbWpfZ2VvX2FyYyRsYXQpCmBgYAoKXAoKV2UgYXJlIGluIGJ1c2luZXNzISBXZSBoYXZlIHRha2VuIGFkZHJlc3NlcyBhbmQgY29udmVydGVkIHRoZW0gaW50byBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlISBJIHRoaW5rIHdlIG5lZWQgYSBiYWRnZSEKIVt0aWR5Z2VvY29kZXIgQmFkZ2VdKHRpZHlnZW9jb2Rlcl9oZXgucG5nKSAKClwKCkJvbnVzIGV4ZXJjaXNlISBMZXQncyB0YWtlIHRoZXNlIGFkZHJlc3NlcyBhbmQgKnJldmVyc2UgZ2VvY29kZSB0aGVtKi4gVGhhdCdzIGp1c3QgYSBmYW5jeSB3YXkgb2Ygc2F5aW5nIHRoYXQgd2Ugd2lsbCB0YWtlIGxhdGl0dWRlIGFuZCBsb25naXR1ZGUgZGF0YSBhbmQgY29udmVydCBpdCBpbnRvIHJlYWRhYmxlIGFkZHJlc3Nlcy4gV2UgdXNlIHRoZSBhcHRseSBuYW1lZCBmdW5jdGlvbiBgcmV2ZXJzZV9nZW9jb2RlYCBhbmQgc3BlY2lmeSB3aGljaCBjb2x1bW5zIHRvIGxvb2sgYXQgKCpsYXQqIGFuZCAqbG9uZyopLCB0aGUgbWV0aG9kIHdlIHdhbnQgZm9yIGdlb2NvZGluZywgIGFuZCB3aGF0IHdlIHdhbnQgdGhlICphZGRyZXNzKiBjb2x1bW4gdG8gYmUgbmFtZWQuIFRoZW4gd2UgYHNlbGVjdGAgb3V0IHNvbWUgY29sdW1ucyB0aGF0IHdlIGFyZW4ndCByZWFsbHkgaW50ZXJlc3RlZCBpbi4gUmVtZW1iZXIsIHdlIGFyZSBkb2luZyB0aGlzIHRoZSAqKnRpZHkqKiB3YXkgc28gd2UgYXJlIHVzaW5nIGAlPiVgIHBpcGVzLgoKYGBge3J9CnJldmVyc2UgPC0gc2ZfbWpfZ2VvX2FyYyAlPiUKICByZXZlcnNlX2dlb2NvZGUobGF0ID0gbGF0LCBsb25nID0gbG9uZywgbWV0aG9kID0gJ2FyY2dpcycsCiAgICAgICAgICAgICAgICAgIGFkZHJlc3MgPSBhZGRyZXNzX2ZvdW5kKSAlPiUKICBzZWxlY3QoLWBCdXNpbmVzcyBPd25lcmAsLWBCdXNpbmVzcyBTdHJ1Y3R1cmVgLC1gTGljZW5zZSBOdW1iZXJgLC1gTGljZW5zZSBUeXBlYCwtU3RhdHVzLC1gSXNzdWUgRGF0ZWAsLWBFeHBpcmF0aW9uIERhdGVgLC1BY3Rpdml0aWVzLC1gQWR1bHQtVXNlL01lZGljaW5hbGApCmhlYWQocmV2ZXJzZSkKYGBgCgpMb29raW5nIGF0ICpQcmVtaXNlIEFkZHJlc3MqIGFuZCAqYWRkcmVzc19mb3VuZCogd2UgY2FuIHNlZSB0aGF0IHdlIGRpZCBwcmV0dHkgd2VsbCEgTm90IHBlcmZlY3QsIGJ1dCBtb3N0IGFyZSB0aGUgcmlnaHQgYWRkcmVzcyBvciBhIGZldyBkb29ycyBkb3duLiBXZWxsIGRvbmUhCgpcCgojIHNmOiBPdXIgZ28gdG8gcGFja2FnZSBmb3IgdmVjdG9yIGRhdGEKCkFsdGhvdWdoIHRoZXJlIGFyZSBhIGZldyB3YXlzIHRvIHdvcmsgd2l0aCB2ZWN0b3Igc3BhdGlhbCBkYXRhIGluIFIsIHdlIHdpbGwgZm9jdXMgb24gdGhlICoqc2YqKiBwYWNrYWdlIGluIHRoaXMgY291cnNlLiBUaGUgbWFqb3JpdHkgb2Ygc3BhdGlhbCBmb2xrcyBpbiBSIGhhdmUgc2hpZnRlZCB0byAqKnNmKiogZm9yIHZlY3RvciBkYXRhLCBhbmQgc28gaXQgbWFrZXMgc2Vuc2UgdG8gZm9jdXMgb24gaXQgaW4gdGhlIGNsYXNzLgoKUHJvY2Vzc2luZyBzcGF0aWFsIGRhdGEgaXMgdmVyeSBzaW1pbGFyIHRvIG5vbnNwYXRpYWwgZGF0YSB0aGFua3MgdG8gdGhlIHBhY2thZ2UgKipzZioqLCB3aGljaCBpcyB0aWR5IGZyaWVuZGx5LiAqKnNmKiogc3RhbmRzIGZvciBzaW1wbGUgZmVhdHVyZXMuIFRoZSBbU2ltcGxlIEZlYXR1cmVzIHN0YW5kYXJkXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9TaW1wbGVfRmVhdHVyZXMpIGRlZmluZXMgYSBzaW1wbGUgZmVhdHVyZSBhcyBhIHJlcHJlc2VudGF0aW9uIG9mIGEgcmVhbCB3b3JsZCBvYmplY3QgYnkgYSBwb2ludCBvciBwb2ludHMgdGhhdCBtYXkgb3IgbWF5IG5vdCBiZSBjb25uZWN0ZWQgYnkgc3RyYWlnaHQgbGluZSBzZWdtZW50cyB0byBmb3JtIGxpbmVzIG9yIHBvbHlnb25zLiBBIGZlYXR1cmUgaXMgdGhvdWdodCBvZiBhcyBhIHRoaW5nLCBvciBhbiBvYmplY3QgaW4gdGhlIHJlYWwgd29ybGQsIHN1Y2ggYXMgYSBidWlsZGluZyBvciBhIHRyZWUuIEEgY291bnR5IGNhbiBiZSBhIGZlYXR1cmUuIEFzIGNhbiBhIGNpdHkgYW5kIGEgbmVpZ2hib3Job29kLiBGZWF0dXJlcyBoYXZlIGEgZ2VvbWV0cnkgZGVzY3JpYmluZyB3aGVyZSBvbiBFYXJ0aCB0aGUgZmVhdHVyZXMgYXJlIGxvY2F0ZWQsIGFuZCB0aGV5IGhhdmUgYXR0cmlidXRlcywgd2hpY2ggZGVzY3JpYmUgb3RoZXIgcHJvcGVydGllcy4gCgpOb3cgbGV0J3MgZ2V0IG91ciBoYW5kcyBkaXJ0eSB3b3JraW5nIHdpdGggc29tZSBzcGF0aWFsIGRhdGEuCgpcCgojIFZlY3RvcnM6IEltcG9ydCBDYW5jZXIgUG9pbnQgRGF0YQoKRm9yIHRoaXMgbGFiLCB3ZSB3aWxsIHByaW1hcmlseSBiZSB3b3JraW5nIHdpdGggdGhlIFsqTWFwR0FNKiBwYWNrYWdlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvTWFwR0FNL2luZGV4Lmh0bWwpLiBJZiB5b3UgZ28gdG8gdGhlIGxpbmssIHlvdSBjYW4gcmVhZCB0aGUgcmVmZXJlbmNlIG1hbnVhbCBvbiB0aGUgdmFyaW91cyBkYXRhc2V0cyBhdmFpbGFibGUgaW4gdGhlIHBhY2thZ2UuIEZvciB0aGlzIGxhYiwgd2Ugd2lsbCBtYWlubHkgYmUgd29ya2luZyB3aXRoIHRoZSAqQ0FkYXRhKiBkYXRhc2V0LiBXaGlsZSB0aGV5IGFyZSBiYXNlZCBvbiByZWFsIHBhdHRlcm5zIGV4cGVjdGVkIGluIG9ic2VydmF0aW9uYWwgZXBpZGVtaW9sb2dpYyBzdHVkaWVzLCB0aGVzZSBkYXRhIGhhdmUgYmVlbiBzaW11bGF0ZWQgYW5kIGFyZSBmb3IgdGVhY2hpbmcgcHVycG9zZXMgb25seS4gVGhlIGRhdGEgY29udGFpbiA1MDAwIHNpbXVsYXRlZCBvdmFyaWFuIGNhbmNlciBjYXNlcy4gVGhpcyBpcyBhIGNvaG9ydCB3aXRoICoqdGltZSB0byBtb3J0YWxpdHkqKiBtZWFzdXJlZCwgYnV0IGZvciB0aGUgcHVycG9zZXMgb2Ygb3VyIGNsYXNzLCB3ZSB3aWxsIGNvbmR1Y3Qgc2ltcGxlIHRhYnVsYXIgYW5hbHlzZXMgbG9va2luZyBhdCBhc3NvY2lhdGlvbnMgYmV0d2VlbiBzcGF0aWFsIGV4cG9zdXJlcyB3aXRoIG1vcnRhbGl0eSBhdCBlbmQgb2YgZm9sbG93LXVwLgoKVGhlICpDQWRhdGEqIGRhdGFzZXQgY29udGFpbnMgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXMKCi0gKnRpbWUqIChmb2xsb3ctdXAgdGltZSB0byBlaXRoZXIgZXZlbnQgb2YgYmVpbmcgY2Vuc29yZWQpCi0gKmV2ZW50KiAoMT1kZWFkLCAwPWNlbnNvcmVkKQotICpYKiAoTGF0aXR1ZGUpCi0gKlkqIChMb25naXR1ZGUpCi0gKkFHRSogKGFnZSBpbiB5ZWFycykKLSAqSU5TKiAoaW5zdXJhbmNlIHN0YXR1cywgY2F0ZWdvcmljYWwpCgpcCgpTbyBsZXQncyBicmluZyBpbiB0aGUgKkNBZGF0YSogZGF0YXNldCBhbmQgaGF2ZSBhIGxvb2sgYXQgaXQuIAoKYGBge3J9CiNMb2FkIENBZGF0YSBkYXRhc2V0IGZyb20gTWFwR0FNIHBhY2thZ2UKZGF0YShDQWRhdGEpCmNhX3B0cyA8LSBDQWRhdGEKc3VtbWFyeShjYV9wdHMpCmBgYAoKXAoKT0ssIHNvIHRoZSB2YXJpYWJsZXMgbG9vayBncmVhdC4gSXMgaXQgYSBzcGF0aWFsIGRhdGFzZXQgdGhhdCBjYW4gYmUgcmVjb2duaXplZCBieSBSPyBOb3QganVzdCB5ZXQuIFdlIGtub3cgdGhhdCAqWCogaXMgbGlrZWx5IHNvbWUgc29ydCBvZiBsb25naXR1ZGUgY29sdW1uIGFuZCAqWSogaXMgbGlrZWx5IHNvbWUgc29ydCBvZiBsYXRpdHVkZSBjb2x1bW4sIGFsdGhvdWdoIHRoZXkgZG9uJ3QgZXhhY3RseSBsb29rIHJpZ2h0LiBXZSBoYXZlIHRvIHRlbGwgUiB0aGF0IHRoZSBYIGFuZCBZIGNvb3JkaW5hdGVzIGFyZSBzcGF0aWFsIGRhdGEgdXNpbmcgdGhlIGBzdF9hc19zZmAgZnVuY3Rpb24gaW4gKipzZioqLiBXaXRoIHRoaXMgY29tbWFuZCwgd2UgY2FuIHNwZWNpZnkgd2hpY2ggY29vcmRpbmF0ZXMgUiBzaG91bGQgbG9vayBhdCBmb3IgbG9uZ2l0dWRlIGFuZCBsYXRpdHVkZSB3aXRoIHRoZSBgY29vcmRzPWMoKWAgZnVuY3Rpb24uCgpcCgpgYGB7cn0KY2FfcHRzIDwtIHN0X2FzX3NmKENBZGF0YSwgY29vcmRzPWMoIlgiLCJZIikpCmBgYAoKXAoKTGV0J3MgY2hlY2sgdGhlIGNvb3JkaW5hdGUgcmVmZXJlbmNlIHN5c3RlbSAoQ1JTKSB1c2luZyB0aGUgYHN0X2Nyc2AgY29tbWFuZCBpbiB0aGUgKipzZioqIHBhY2thZ2UuCgpgYGB7cn0Kc3RfY3JzKGNhX3B0cykKYGBgCgpIbW1tLCBOQS4gVGhhdCBzdGlsbCBkb2Vzbid0IGxvb2sgZ29vZC4gU28gaG93IGRvIHdlIG1ha2UgdGhpcyBhIHNwYXRpYWwgZmlsZT8gV2Ugd2lsbCBuZWVkIHRvIGFkZCBhIENSUy4gCgpcCgojIyBBZGQgY29vcmRpbmF0ZSByZWZlcmVuY2Ugc3lzdGVtCgpMZXQncyBhZGQgYSBDUlMgYnkgdXNpbmcgYHN0X3NldF9zZmAgZnJvbSB0aGUgKipzZioqIHBhY2thZ2UuIFdlIGdldCB0aGUgQ1JTIGZvciB0aGlzIGRhdGFzZXQgZnJvbSB0aGUgTWFwR0FNIGRvY3VtZW50YXRpb24gKGRvbid0IHdvcnJ5LS1pdCB0b29rIG1lIGZvcmV2ZXIgdG8gZmluZCB0aGlzLCBidXQgdXN1YWxseSB0aGlzIGlzIG11Y2ggZWFzaWVyIHRvIGZpbmQpLiBUaGVuIHdlIHdpbGwgZG91YmxlIGNoZWNrIHRoZSBDUlMuCgpgYGB7cn0KI0xvYWQgdGhlIHByb2plY3Rpb24gaW50byBhbiBvYmplY3QgY2FsbGVkIGNhX3Byb2oKCmNhX3Byb2ogPC0gIitwcm9qPWxjYyArbGF0XzE9NDAgK2xhdF8yPTQxLjY2NjY2NjY2NjY2NjY2IAogICAgICAgICAgICAgK2xhdF8wPTM5LjMzMzMzMzMzMzMzMzM0ICtsb25fMD0tMTIyICt4XzA9MjAwMDAwMCAKICAgICAgICAgICAgICt5XzA9NTAwMDAwLjAwMDAwMDAwMDIgK2VsbHBzPUdSUzgwIAogICAgICAgICAgICAgK2RhdHVtPU5BRDgzICt1bml0cz1tICtub19kZWZzIgoKI1NldCBDUlMKY2FfcHRzX2NycyA8LSBzdF9zZXRfY3JzKGNhX3B0cywgY2FfcHJvaikKCiNMb29rIGF0IGRhdGFzZXQKc3VtbWFyeShjYV9wdHNfY3JzKSAKCiNDaGVjayB0aGUgQ1JTCnN0X2NycyhjYV9wdHNfY3JzKQpgYGAKClwKCiMgdG1hcDogTWFwcGluZyB2ZWN0b3IgZGF0YQoKTmljZSEgV2UgaGF2ZSBhIHNwYXRpYWwgZGF0YXNldC4gVGhhdCAqZ2VvbWV0cnkqIGNvbHVtbiBpcyBob3cgKipzZioqIHN0b3JlcyB0aGUgZ2VvZ3JhcGhpYyBkYXRhLCBhbmQgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSB0aGVyZSBhcmUgbG9va2luZyBhIGJpdCBtb3JlIGxpa2Ugd2Ugd291bGQgZXhwZWN0LiBBbmQgd2UgZGVmaW5pdGVseSBoYXZlIGEgZnVsbCBDUlMgd2l0aCBhbGwgc29ydHMgb2YgaW5mby4gT0ssIGxldCdzIHBsb3Qgb3VyIGRhdGEgdG8gbWFrZSBzdXJlIHRoZXkgbG9vayBzcGF0aWFsISBXZSB3aWxsIHVzZSB0aGUgKip0bWFwKiogcGFja2FnZSB0byBwbG90IHRoZSBwb2ludHMuIFdlIHdpbGwgZmlyc3Qgc3BlY2lmeSB0aGUgYHRtYXBfbW9kZWAgb2YgInZpZXciIHdoaWNoIGlzIGludGVyYWN0aXZlLiBUaGVyZSdzIGFsc28gYSAicGxvdCIgb3B0aW9uIHdoaWNoIGlzIG5pY2UgZm9yIG1ha2luZyBleHBvcnRhYmxlIGZpZ3VyZXMuIFdlIHdpbGwgdGhlbiBjcmVhdGUgYW4gb2JqZWN0IGNhbGxlZCAqY2FuY2VyX21hcCogYW5kIHRoZW4gYWRkIGEgbGF5ZXIgd2l0aCBgdG1fc2hhcGUoKWAuIFRoaXMgYWxsb3dzIHVzIHRvIGNvbWJpbmUgc2V2ZXJhbCBtYXBzIGludG8gb25lLCBvciB0byBhZGQgbGF5ZXJzIG9uIHRvcCBvZiBlYWNoIG90aGVyLiBUaGVuIHdlIGhhdmUgdG8gc3BlY2lmeSBhIGxldmVsIG9mIHRoYXQgbGF5ZXIgdG8gZGlzcGxheS4gSGVyZSB3ZSB3aWxsIHVzZSBgdG1fZG90cygpYCB0byB0byBwbG90IHRoZSBwb2ludHMuIEluIG91ciBvcHRpb25zLCB3ZSBzcGVjaWZ5IHRoZSBzaXplIHdpdGggYHNpemU9YC4gCmBgYHtyfQp0bWFwX21vZGUoInZpZXciKQpjYW5jZXJfbWFwID0gdG1fc2hhcGUoY2FfcHRzX2NycykgKyB0bV9kb3RzKHNpemU9MC41KQpjYW5jZXJfbWFwCmBgYAoKXAoKTGV0J3MgcGxheSBhcm91bmQgd2l0aCBzb21lIG9mIHRoZSBvcHRpb25zLiBXZSBjYW4gY2hhbmdlIHRoZSBjb2xvciB3aXRoIHRoZSBgY29sPWAgb3B0aW9uLiBXZSBjYW4gbWFrZSB0aGUgZG90cyBzbWFsbGVyIGJ5IHNwZWNpZnlpbmcgdGhlIGBzaXplPWAgb3B0aW9uLiBBbmQgd2UgY2FuIGNoYW5nZSB0aGUgdHJhbnNwYXJlbmN5IG9mIHRoZSBwb2ludHMgd2l0aCB0aGUgYGFscGhhPWAgb3B0aW9uLgpgYGB7cn0KY2FuY2VyX21hcF9zbWFsbCA9IHRtX3NoYXBlKGNhX3B0c19jcnMpICsgdG1fZG90cyhjb2wgPSAiYmx1ZSIsIHNpemUgPSAwLjMsIGFscGhhID0gMC41KQpjYW5jZXJfbWFwX3NtYWxsCmBgYAoKXAoKTGV0J3MgbWFwIHRoZSBwb2ludHMgY29sb3IgY29kZWQgYnkgdGhlIHZhcmlhYmxlICpldmVudCosIG9yIHdoZXRoZXIgb3Igbm90IHRoZSBwYXJ0aWNpcGFudCBkaWVkIG92ZXIgZm9sbG93dXAuIFdlIGRvIHRoYXQgYnkgc3BlY2lmeWluZyB0aGF0IHRoZSBjb2xvciAoYGNvbD1gKSBpcyBiYXNlZCBvbiB0aGUgY29sdW1uICpldmVudCouIFdlIGhhdmUgdG8gc3BlY2lmeSB0aGF0ICpldmVudCogaXMgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSB3aXRoIGBzdHlsZT0iY2F0ImAuIApgYGB7cn0KY2FuY2VyX21hcF9ldmVudHMgPSB0bV9zaGFwZShjYV9wdHNfY3JzKSArIHRtX2RvdHMoc2l6ZT0wLjMsIGNvbD0iZXZlbnQiLCBzdHlsZT0iY2F0IikKY2FuY2VyX21hcF9ldmVudHMKYGBgCgpcCgpMZXQncyBzZWUgaWYgd2UgY2FuIGNoYW5nZSB1cCB0aGUgY29sb3Igc2NoZW1lLgpgYGB7cn0KY2FuY2VyX21hcF9ldmVudHNfcmcgPSB0bV9zaGFwZShjYV9wdHNfY3JzKSArIHRtX2RvdHMoY29sID0gImV2ZW50IiwgcGFsZXR0ZSA9IGMoIjAiID0gImdyYXkiLCAiMSIgPSAicmVkIiksIHN0eWxlPSJjYXQiKQpjYW5jZXJfbWFwX2V2ZW50c19yZwpgYGAKClwKCkxvb2tpbmcgZ29vZCEgWW91IGVhcm5lZCB5b3Vyc2VsZiBhIHRtYXAgYmFkZ2UuIE5vdyBnZXQgeW91cnNlbGYgYSBbY29va2llXShodHRwczovL3lvdXR1LmJlLzNzb0M2VkFEcGN3P3NpPTRuakJpYjNqV1BzVVlhRVMpLgoKXAoKIVt0bWFwIEJhZGdlXSh0bWFwbG9nby5wbmcpCgpcCgojIERvd25sb2FkaW5nIENlbnN1cyBEYXRhCgpPbmUgb2YgdGhlIHByaW1hcnkgc291cmNlcyBvZiBkYXRhIHRoYXQgd2XigJlsbCBiZSB1c2luZyBpbiB0aGlzIGNsYXNzIGlzIHRoZSBVbml0ZWQgU3RhdGVzIERlY2VubmlhbCBDZW5zdXMgYW5kIHRoZSBBbWVyaWNhbiBDb21tdW5pdHkgU3VydmV5LiBUaGVyZSBhcmUgdHdvIHdheXMgdG8gYnJpbmcgQ2Vuc3VzIGRhdGEgaW50byBSOiBVc2luZyBhbmQgQVBJIG9yIGRvd25sb2FkaW5nIGl0IGZyb20gYW4gb25saW5lIHNvdXJjZS4KCioqTm90ZSB0aGF0IHdlIHdpbGwgZ2F0aGVyIEFDUyBkYXRhIGZyb20gYWxsIHNvdXJjZXMuIENlbnN1cyBib3VuZGFyaWVzIGNoYW5nZWQgaW4gMjAyMCwgd2hpY2ggbWVhbnMgdGhhdCAyMDE2LTIwMjAgYW5kIGxhdGVyIGRhdGEgd2lsbCBub3QgY29tcGxldGVseSBtZXJnZSB3aXRoIEFDUyBkYXRhIGJlZm9yZSAyMDIwLiBTbyBtYWtlIHN1cmUgeW91IG1lcmdlIDIwMjAgZGF0YSBvbmx5IHdpdGggMjAyMCBkYXRhIChidXQgeW91IGNhbiBtZXJnZSAyMDE5IGRhdGEgd2l0aCBkYXRhIGJldHdlZW4gMjAxMC0yMDE5KS4gVGhpcyBpcyBlc3BlY2lhbGx5IGltcG9ydGFudCBmb3IgdHJhY3QgZGF0YSwgd2l0aCBtYW55IG5ldyB0cmFjdHMgY3JlYXRlZCBpbiAyMDIwIGFuZCBleGlzdGluZyB0cmFjdHMgZXhwZXJpZW5jaW5nIGRyYW1hdGljIGNoYW5nZXMgaW4gdGhlaXIgYm91bmRhcmllcyBiZXR3ZWVuIDIwMTAgYW5kIDIwMjAuIFNlZSB0aGUgaW1wYWN0IG9mIHRyYWN0IGJvdW5kYXJ5IGNoYW5nZXMgYmV0d2VlbiAyMDAwIGFuZCAyMDEwIFtoZXJlXShodHRwczovL2NyZDIzMC5naXRodWIuaW8vY2Vuc3VzZ2VvZ3JhcGh5Lmh0bWwpLioqIFlvdSBtYXkgYWxzbyBleHBsb3JlIHRoZSBbTmVpZ2hib3Job29kIENoYW5nZSBEYXRhYmFzZV0oaHR0cHM6Ly9zZWFyY2gubGlicmFyeS51Y2RhdmlzLmVkdS92aWV3L2FjdGlvbi91cmVzb2x2ZXIuZG8/b3BlcmF0aW9uPXJlc29sdmVTZXJ2aWNlJnBhY2thZ2Vfc2VydmljZV9pZD0yODEwNDIyMzg2MDAwMzEyNiZpbnN0aXR1dGlvbklkPTMxMjYmY3VzdG9tZXJJZD0zMTI1JlZFPXRydWUpIHdoaWNoIGlzIGF2YWlsYWJsZSB0aHJvdWdoIHRoZSBVQyBEYXZpcyBsaWJyYXJ5LCBhbmQgaXMgYSBkYXRhc2V0IHRoYXQgaW5jb3Jwb3JhdGVzIHRyYWN0IGJvdW5kYXJ5IGNoYW5nZXMgb3ZlciB0aW1lLiBXZSBhcmUgd29ya2luZyBvbiBhY3F1aXJpbmcgdGhlIDIwMjAgZGF0YSB0aGVyZSEKClwKCiMjIERvd25sb2FkIENlbnN1cyBkYXRhIGZyb20gYW4gb25saW5lIHNvdXJjZQoKVGhlIGZpcnN0IHdheSB0byBvYnRhaW4gQ2Vuc3VzIGRhdGEgaXMgdG8gZG93bmxvYWQgdGhlbSBkaXJlY3RseSBmcm9tIHRoZSB3ZWIgb250byB5b3VyIGhhcmQgZHJpdmUuIFRoZXJlIGFyZSBzZXZlcmFsIHdlYnNpdGVzIHdoZXJlIHlvdSBjYW4gZG93bmxvYWQgQ2Vuc3VzIGRhdGEgaW5jbHVkaW5nIFtTb2NpYWwgRXhwbG9yZXJdKGh0dHBzOi8vd3d3LnNvY2lhbGV4cGxvcmVyLmNvbS8pIGFuZCBbUG9saWN5TWFwXShodHRwczovL3VjZGF2aXMucG9saWN5bWFwLmNvbS9tYXBzKSwgd2hpY2ggd2UgaGF2ZSBmcmVlIGFjY2VzcyB0byBhcyBVQyBEYXZpcyBhZmZpbGlhdGVzLCBhbmQgdGhlIFtOYXRpb25hbCBIaXN0b3JpY2FsIEdlb2dyYXBoaWMgSW5mb3JtYXRpb24gU3lzdGVtIChOSEdJUyldKGh0dHBzOi8vd3d3Lm5oZ2lzLm9yZy8pLCB3aGljaCBpcyBmcmVlIGZvciBldmVyeW9uZS4gVG8gZmluZCBvdXQgaG93IHRvIGRvd25sb2FkIGRhdGEgZnJvbSBQb2xpY3lNYXAgYW5kIE5IR0lTLCBjaGVjayBvdXQgdHV0b3JpYWxzIFtoZXJlXShodHRwczovL3BvbGljeW1hcC5oZWxwZG9jcy5pby9kYXRhLWRvd25sb2FkKSBhbmQgW2hlcmVdKGh0dHBzOi8vd3d3Lm5oZ2lzLm9yZy91c2VyLXJlc291cmNlcy91c2Vycy1ndWlkZSkuCgpcCgojIyBVc2UgdGhlIENlbnN1cyBBUEkgYW5kIHRpZHljZW5zdXMKClRoZSBvdGhlciB3YXkgdG8gYnJpbmcgQ2Vuc3VzIGRhdGEgaW50byBSIGlzIHRvIHVzZSB0aGUgW0NlbnN1cyBBcHBsaWNhdGlvbiBQcm9ncmFtIEludGVyZmFjZSAoQVBJKV0oaHR0cHM6Ly93d3cuY2Vuc3VzLmdvdi9kYXRhL2RldmVsb3BlcnMvZ3VpZGFuY2UvYXBpLXVzZXItZ3VpZGUuV2hhdF9pc190aGVfQVBJLmh0bWwpLiBBbiBBUEkgYWxsb3dzIGZvciBkaXJlY3QgcmVxdWVzdHMgZm9yIGRhdGEgaW4gbWFjaGluZS1yZWFkYWJsZSBmb3JtLiBUaGF0IGlzLCByYXRoZXIgdGhhbiB5b3UgaGF2aW5nIHRvIG5hdmlnYXRlIHRvIHNvbWUgd2Vic2l0ZSwgc2Nyb2xsIGFyb3VuZCB0byBmaW5kIGEgZGF0YXNldCwgZG93bmxvYWQgdGhhdCBkYXRhc2V0IG9uY2UgeW91IGZpbmQgaXQsIHNhdmUgdGhhdCBkYXRhIG9udG8geW91ciBoYXJkIGRyaXZlLCBhbmQgdGhlbiBicmluZyB0aGUgZGF0YSBpbnRvIFIsIHlvdSBqdXN0IHRlbGwgUiB0byByZXRyaWV2ZSBkYXRhIGRpcmVjdGx5IGZyb20gdGhlIHNvdXJjZSB1c2luZyBvbmUgb3IgdHdvIGxpbmVzIG9mIGNvZGUuCgpJbiBvcmRlciB0byBkaXJlY3RseSBkb3dubG9hZCBkYXRhIGZyb20gdGhlIENlbnN1cyBBUEksIHlvdSBuZWVkIGEga2V5LiBZb3UgY2FuIHNpZ24gdXAgZm9yIGEgZnJlZSBrZXkgW2hlcmVdKGh0dHA6Ly9hcGkuY2Vuc3VzLmdvdi9kYXRhL2tleV9zaWdudXAuaHRtbCksIHdoaWNoIHlvdSBzaG91bGQgaGF2ZSBhbHJlYWR5IGRvbmUgYmVmb3JlIHRoZSBsYWIuIFR5cGUgeW91ciBrZXkgaW4gcXVvdGVzIHVzaW5nIHRoZSBjZW5zdXNfYXBpX2tleSgpIGNvbW1hbmQuCmBgYHtyLCBldmFsID0gRkFMU0V9CmNlbnN1c19hcGlfa2V5KCJZT1VSIEFQSSBLRVkgR09FUyBIRVJFIiwgaW5zdGFsbCA9IFRSVUUpCmBgYAoKYGBge3IsIGVjaG89RkFMU0UsIGV2YWw9RkFMU0V9CmNlbnN1c19hcGlfa2V5KCI1ZDY4OTM1Yzk2YzI2ZWU2N2NhNTJlYjk3M2Q3MWU0YTdiODQ5MGFkIiwgaW5zdGFsbCA9IFRSVUUsIG92ZXJ3cml0ZT1UUlVFKQpgYGAKClwKClRoZSBvcHRpb24gYGluc3RhbGwgPSBUUlVFYCBzYXZlcyB0aGUgQVBJIGtleSBpbiB5b3VyIFIgZW52aXJvbm1lbnQsIHdoaWNoIG1lYW5zIHlvdSBkb27igJl0IGhhdmUgdG8gcnVuIGBjZW5zdXNfYXBpX2tleSgpYCBldmVyeSBzaW5nbGUgdGltZS4gVGhlIGZ1bmN0aW9uIGZvciBkb3dubG9hZGluZyBBbWVyaWNhbiBDb21tdW5pdHkgU3VydmV5IChBQ1MpIENlbnN1cyBkYXRhIGlzIGBnZXRfYWNzKClgLiBUaGUgY29tbWFuZCBmb3IgZG93bmxvYWRpbmcgZGVjZW5uaWFsIENlbnN1cyBkYXRhIGlzIGBnZXRfZGVjZW5uaWFsKClgLiBCb3RoIGZ1bmN0aW9ucyBjb21lIGZyb20gdGhlICoqdGlkeWNlbnN1cyoqIHBhY2thZ2UsIHdoaWNoIGFsbG93cyB1c2VycyB0byBpbnRlcmZhY2Ugd2l0aCB0aGUgVVMgQ2Vuc3VzIEJ1cmVhdeKAmXMgZGVjZW5uaWFsIENlbnN1cyBhbmQgQW1lcmljYW4gQ29tbXVuaXR5IFN1cnZleSBBUElzLiBHZXR0aW5nIHZhcmlhYmxlcyB1c2luZyB0aGUgQ2Vuc3VzIEFQSSByZXF1aXJlcyBrbm93aW5nIHRoZSB2YXJpYWJsZSBJRCAtIGFuZCB0aGVyZSBhcmUgdGhvdXNhbmRzIG9mIHZhcmlhYmxlcyAoYW5kIHRodXMgdGhvdXNhbmRzIG9mIElEcykgYWNyb3NzIHRoZSBkaWZmZXJlbnQgQ2Vuc3VzIGZpbGVzLiBUbyByYXBpZGx5IHNlYXJjaCBmb3IgdmFyaWFibGVzLCB1c2UgdGhlIGNvbW1hbmRzIGBsb2FkX3ZhcmlhYmxlcygpYCBhbmQgYFZpZXcoKWAuIEJlY2F1c2Ugd2XigJlsbCBiZSB1c2luZyB0aGUgQUNTIGluIHRoaXMgZ3VpZGUsIGxldOKAmXMgY2hlY2sgdGhlIHZhcmlhYmxlcyBpbiB0aGUgbW9zdCByZWNlbnQgMjAyMyA1LXllYXIgQUNTICgyMDE5LTIwMjMpIHVzaW5nIHRoZSBmb2xsb3dpbmcgY29tbWFuZHMuCmBgYHtyfQphY3MyMDIzIDwtIGxvYWRfdmFyaWFibGVzKDIwMjMsICJhY3M1IiwgY2FjaGUgPSBUUlVFKQpWaWV3KGFjczIwMjMpCmBgYAoKXAoKQSB3aW5kb3cgc2hvdWxkIGhhdmUgcG9wcGVkIHVwIHNob3dpbmcgeW91IGEgcmVjb3JkIGxheW91dCBvZiB0aGUgMjAxOS0yMDIzIEFDUy4gVG8gc2VhcmNoIGZvciBzcGVjaWZpYyBkYXRhLCBzZWxlY3Qg4oCcRmlsdGVy4oCdIGxvY2F0ZWQgYXQgdGhlIHRvcCBsZWZ0IG9mIHRoaXMgd2luZG93IGFuZCB1c2UgdGhlIHNlYXJjaCBib3hlcyB0aGF0IHBvcCB1cC4gRm9yIGV4YW1wbGUsIHR5cGUgaW4g4oCcSGlzcGFuaWPigJ0gaW4gdGhlIGJveCB1bmRlciDigJxMYWJlbOKAnS4gWW91IHNob3VsZCBzZWUgbmVhciB0aGUgdG9wIG9mIHRoZSBsaXN0IHRoZSBmaXJzdCBzZXQgb2YgdmFyaWFibGVzIHdl4oCZbGwgd2FudCB0byBkb3dubG9hZCAtIHJhY2UvZXRobmljaXR5LiBBbm90aGVyIHdheSBvZiBmaW5kaW5nIHZhcmlhYmxlIG5hbWVzIGlzIHRvIHNlYXJjaCB0aGVtIHVzaW5nIFtTb2NpYWwgRXhwbG9yZXJdKGh0dHBzOi8vd3d3LnNvY2lhbGV4cGxvcmVyLmNvbS9kYXRhL21ldGFkYXRhLykuIENsaWNrIG9uIHRoZSBhcHByb3ByaWF0ZSBzdXJ2ZXkgZGF0YSB5ZWFyIGFuZCB0aGVuIOKAnEFtZXJpY2FuIENvbW11bml0eSBTdXJ2ZXkgVGFibGVz4oCdLCB3aGljaCB3aWxsIHRha2UgeW91IHRvIGEgbGlzdCBvZiB2YXJpYWJsZXMgd2l0aCB0aGVpciBDZW5zdXMgSURzLgoKTGV04oCZcyBleHRyYWN0IHJhY2UvZXRobmljaXR5IGRhdGEgYW5kIHRvdGFsIHBvcHVsYXRpb24gZm9yIFtDYWxpZm9ybmlhIGNvdW50aWVzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MaXN0X29mX2NvdW50aWVzX2luX0NhbGlmb3JuaWEpIHVzaW5nIHRoZSBgZ2V0X2FjcygpYCBjb21tYW5kLgoKXAoKYGBge3J9CmNhIDwtIGdldF9hY3MoZ2VvZ3JhcGh5ID0gImNvdW50eSIsIAogICAgICAgICAgICAgIHllYXIgPSAyMDIzLAogICAgICAgICAgICAgIHZhcmlhYmxlcyA9IGModHBvcHIgPSAiQjAzMDAyXzAwMSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbmh3aGl0ZSA9ICJCMDMwMDJfMDAzIiwgbmhibGsgPSAiQjAzMDAyXzAwNCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbmhhc24gPSAiQjAzMDAyXzAwNiIsIGhpc3AgPSAiQjAzMDAyXzAxMiIpLCAKICAgICAgICAgICAgICBzdGF0ZSA9ICJDQSIsCiAgICAgICAgICAgICAgc3VydmV5ID0gImFjczUiLAogICAgICAgICAgICAgIG91dHB1dCA9ICJ3aWRlIikKCmBgYAoKXAoKSW4gdGhlIGFib3ZlIGNvZGUsIHdlIHNwZWNpZmllZCB0aGUgZm9sbG93aW5nIGFyZ3VtZW50cwoKLSBgZ2VvZ3JhcGh5YDogVGhlIGxldmVsIG9mIGdlb2dyYXBoeSB3ZSB3YW50IHRoZSBkYXRhIGluOyBpbiBvdXIgY2FzZSwgdGhlIGNvdW50eS4gT3RoZXIgZ2VvZ3JhcGhpYyBvcHRpb25zIGNhbiBiZSBmb3VuZCBbaGVyZV0oaHR0cHM6Ly93YWxrZXJrZS5naXRodWIuaW8vdGlkeWNlbnN1cy9hcnRpY2xlcy9iYXNpYy11c2FnZS5odG1sI2dlb2dyYXBoeS1pbi10aWR5Y2Vuc3VzKS4KLSBgeWVhcmA6IFRoZSBlbmQgeWVhciBvZiB0aGUgZGF0YSAoYmVjYXVzZSB3ZSB3YW50IDIwMTYtMjAyMCwgd2UgdXNlIDIwMjApLgotIGB2YXJpYWJsZXNgOiBUaGUgdmFyaWFibGVzIHdlIHdhbnQgdG8gYnJpbmcgaW4gYXMgc3BlY2lmaWVkIGluIGEgdmVjdG9yIHlvdSBjcmVhdGUgdXNpbmcgdGhlIGZ1bmN0aW9uIGBjKClgLiBOb3RlIHRoYXQgd2UgY3JlYXRlZCB2YXJpYWJsZSBuYW1lcyBvZiBvdXIgb3duIChlLmcuIOKAnG5od2hpdGXigJ0pIGFuZCB3ZSBwdXQgdGhlIEFDUyBJRHMgaW4gcXVvdGVzICjigJxCMDMwMDJfMDAz4oCdKS4gSGFkIHdlIG5vdCBkb25lIHRoaXMsIHRoZSB2YXJpYWJsZSBuYW1lcyB3aWxsIGNvbWUgaW4gYXMgdGhleSBhcmUgbmFtZWQgaW4gdGhlIEFDUywgd2hpY2ggYXJlIG5vdCB2ZXJ5IGRlc2NyaXB0aXZlLgotIGBzdGF0ZWA6IFdlIGNhbiBmaWx0ZXIgdGhlIGNvdW50aWVzIHRvIHRob3NlIGluIGEgc3BlY2lmaWMgc3RhdGUuIEhlcmUgaXQgaXMg4oCcQ0HigJ0gZm9yIENhbGlmb3JuaWEuIElmIHdlIGRvbuKAmXQgc3BlY2lmeSB0aGlzLCB3ZSBnZXQgYWxsIGNvdW50aWVzIGluIHRoZSBVbml0ZWQgU3RhdGVzLgotIGBzdXJ2ZXlgOiBUaGUgc3BlY2lmaWMgQ2Vuc3VzIHN1cnZleSB3ZXJlIGV4dHJhY3RpbmcgZGF0YSBmcm9tLiBXZSB3YW50IGRhdGEgZnJvbSB0aGUgNS15ZWFyIEFtZXJpY2FuIENvbW11bml0eSBTdXJ2ZXksIHNvIHdlIHNwZWNpZnkg4oCcYWNzNeKAnS4gVGhlIEFDUyBjb21lcyBpbiAxLSBhbmQgNS15ZWFyIC0gdmFyaWV0aWVzLgotIGBvdXRwdXRgOiBUaGUgYXJndW1lbnQgdGVsbHMgUiB0byByZXR1cm4gYSB3aWRlIGRhdGFzZXQgYXMgb3Bwb3NlZCB0byBhIGxvbmcgZGF0YXNldCAoc2VlIHRoaXMgdmlnbmV0dGUgZm9yIG1vcmUgaW5mbykuCgpBbm90aGVyIHVzZWZ1bCBvcHRpb24gdG8gc2V0IGlzIGBjYWNoZV90YWJsZSA9IFRSVUVgLCBzbyB5b3UgZG9u4oCZdCBoYXZlIHRvIHJlLWRvd25sb2FkIGFmdGVyIHlvdeKAmXZlIGRvd25sb2FkZWQgc3VjY2Vzc2Z1bGx5IHRoZSBmaXJzdCB0aW1lLiBUeXBlIGluIGA/IGdldF9hY3MoKWAgdG8gc2VlIHRoZSBmdWxsIGxpc3Qgb2Ygb3B0aW9ucy4KClwKCkFzIHlvdSBsZWFybmVkIGluIFtMYWIgMV0obGFiMS5odG1sKSwgd2hlbmV2ZXIgeW91IGJyaW5nIGluIGEgZGF0YXNldCwgdGhlIGZpcnN0IHRoaW5nIHlvdSBzaG91bGQgYWx3YXlzIGRvIGlzIHZpZXcgaXQgdG8gZ2V0IGEgc2Vuc2Ugb2YgaXRzIHN0cnVjdHVyZSBhbmQgdG8gbWFrZSBzdXJlIHlvdSBnb3Qgd2hhdCB5b3UgZXhwZWN0ZWQuIE9uZSB3YXkgb2YgZG9pbmcgdGhpcyBpcyB0byB1c2UgdGhlIGBnbGltcHNlKClgIGNvbW1hbmQKCmBgYHtyfQpnbGltcHNlKGNhKQpgYGAKClwKCllvdSBnZXQgYSBxdWljaywgY29tcGFjdCBzdW1tYXJ5IG9mIHlvdXIgdGliYmxlLiBZb3UgY2FuIGFsc28gdXNlIHRoZSBgaGVhZCgpYCBjb21tYW5kLCB3aGljaCBzaG93cyB5b3UgdGhlIGZpcnN0IHNldmVyYWwgcm93cyBvZiB5b3VyIGRhdGEgb2JqZWN0IChgdGFpbCgpYCB3aWxsIGdpdmUgeW91IHRoZSBsYXN0IHNldmVyYWwgcm93cykuCgpgYGB7cn0KaGVhZChjYSkKYGBgCgpcCgpUaGUgdGliYmxlIGNvbnRhaW5zIGNvdW50aWVzIHdpdGggdGhlaXIgZXN0aW1hdGVzIGZvciByYWNlL2V0aG5pY2l0eS4gVGhlc2UgdmFyaWFibGVzIGVuZCB3aXRoIHRoZSBsZXR0ZXIg4oCcReKAnS4gSXQgYWxzbyBjb250YWlucyB0aGUgW21hcmdpbnMgb2YgZXJyb3JdKGh0dHBzOi8vd2Fsa2VyLWRhdGEuY29tL3RpZHljZW5zdXMvYXJ0aWNsZXMvbWFyZ2lucy1vZi1lcnJvci5odG1sKSBmb3IgZWFjaCBlc3RpbWF0ZS4gVGhlc2UgdmFyaWFibGVzIGVuZCB3aXRoIHRoZSBsZXR0ZXIg4oCcTeKAnS4KCioqdGlkeWNlbnN1cyoqIGlzIGEgZ2FtZSBjaGFuZ2VyIGluIGJlaW5nIGFibGUgdG8gYnJpbmcgaW4gQ2Vuc3VzIGRhdGEgaW50byBSIGluIGEgY29udmVuaWVudCwgZmFzdCwgZWZmaWNpZW50IGFuZCB0aWR5IGZyaWVuZGx5IHdheS4gV2XigJlsbCBiZSB1c2luZyB0aGlzIHBhY2thZ2UgaW4gdGhlIG5leHQgbGFiIHRvIGJyaW5nIGluIENlbnN1cyBzcGF0aWFsIGRhdGEuIEFuZCBjb25ncmF0dWxhdGlvbnMhIFlvdeKAmXZlIGp1c3QgZWFybmVkIGFub3RoZXIgYmFkZ2UuIEZhbnRhc3RpYyEKClwKCiFbdGlkeWNlbnN1cyBCYWRnZV0odGlkeWNlbnN1c19zdGlja2VyLnBuZykKClwKCiMgUmVhZGluZyBpbiBkYXRhCiMjIFBvbGljeU1hcAoKVG8gc2F2ZSB1cyB0aW1lLCBJ4oCZdmUgdXBsb2FkZWQgYSBQb2xpY3lNYXAgKFtsaW5rIHRvIC5jc3ZdKGh0dHBzOi8vZ2l0aHViLmNvbS9wamFtZXMtdWNkYXZpcy9TUEgyMTUvYmxvYi9tYWluL1BvbGljeU1hcCUyMERhdGElMjAyMDI1LTAzLTI3JTIwMTkyNTU1JTIwVVRDLmNzdikpIG9uIHRoZSBHaXRodWIgZm9yIHlvdSB0byB1c2UgaW4gdGhpcyBsYWIuIFNhdmUgdGhpcyBmaWxlIGluIHRoZSBzYW1lIGZvbGRlciB3aGVyZSB5b3VyIExhYiAyIFIgTWFya2Rvd24gZmlsZSByZXNpZGVzLiBUbyByZWFkIGluIGEgLmNzdiBmaWxlLCBmaXJzdCBtYWtlIHN1cmUgdGhhdCBSIGlzIHBvaW50ZWQgdG8gdGhlIGZvbGRlciB5b3Ugc2F2ZWQgeW91ciBkYXRhIGludG8uIFR5cGUgaW4gYGdldHdkKClgIHRvIGZpbmQgb3V0IHRoZSBjdXJyZW50IGRpcmVjdG9yeSBhbmQgYHNldHdkKCJkaXJlY3RvcnkgbmFtZSIpYCB0byBzZXQgdGhlIGRpcmVjdG9yeSB0byB0aGUgZm9sZGVyIGNvbnRhaW5pbmcgdGhlIGRhdGEuIAoKRnJvbSBhIE1hYyBsYXB0b3AsIEkgdHlwZSBpbiB0aGUgZm9sbG93aW5nIGNvbW1hbmQgdG8gc2V0IHRoZSBkaXJlY3RvcnkgdG8gdGhlIGZvbGRlciBjb250YWluaW5nIG15IGRhdGEuCgpgYGB7cn0Kc2V0d2QoIi9Vc2Vycy9wamFtZXMxL0Ryb3Bib3gvVUMgRGF2aXMgRm9sZGVycy9TUEggMjE1IEdJUyBhbmQgUHVibGljIEhlYWx0aC9HaXRodWJfV2Vic2l0ZS9TUEgyMTUvIikKYGBgCgpcCgpGb3IgYSBXaW5kb3dzIHN5c3RlbSwgeW91IGNhbiBmaW5kIHRoZSBwYXRod2F5IG9mIGEgZmlsZSBieSByaWdodCBjbGlja2luZyBvbiBpdCBhbmQgc2VsZWN0aW5nIFByb3BlcnRpZXMuIFlvdSB3aWxsIGZpbmQgdGhhdCBpbnN0ZWFkIG9mIGEgZm9yd2FyZCBzbGFzaCBsaWtlIGluIGEgTWFjLCBhIHdpbmRvd3MgcGF0aHdheSB3aWxsIGJlIGluZGljYXRlZCBieSBhIHNpbmdsZSBiYWNrIHNsYXNoIFwuIFIgZG9lc27igJl0IGxpa2UgdGhpcyBiZWNhdXNlIGl0IHRoaW5rcyBvZiBhIHNpbmdsZSBiYWNrIHNsYXNoIGFzIGFuIGVzY2FwZSBjaGFyYWN0ZXIuIFVzZSBpbnN0ZWFkIHR3byBiYWNrIHNsYXNoZXMgXFwKCmBgYHtyIGV2YWw9RkFMU0V9CnNldHdkKCJDOlxcVXNlcnNcXHBqYW1lc1xcRG9jdW1lbnRzXFxVQ0RcXFNwcmluZzIwMjVcXFNQSDIxNVxcTGFic1xcTGFiIDIiKQpgYGAKb3IgYSBmb3J3YXJkIHNsYXNoIC8uCgpgYGB7ciBldmFsPUZBTFNFfQpzZXR3ZCgiQzovVXNlcnMvcGphbWVzL0RvY3VtZW50cy9VQ0QvU3ByaW5nMjAyNS9TUEgyMTUvTGFicy9MYWIgMiIpCmBgYAoKWW91IGNhbiBhbHNvIG1hbnVhbGx5IHNldCB0aGUgd29ya2luZyBkaXJlY3RvcnkgYnkgY2xpY2tpbmcgb24gU2Vzc2lvbiAtPiBTZXQgV29ya2luZyBEaXJlY3RvcnkgLT4gQ2hvb3NlIERpcmVjdG9yeSBmcm9tIHRoZSBtZW51LgoKXAoKT25jZSB5b3XigJl2ZSBzZXQgeW91ciBkaXJlY3RvcnksIHVzZSB0aGUgZnVuY3Rpb24gYHJlYWRfY3N2KClgLCB3aGljaCBpcyBhIHBhcnQgb2YgdGhlICoqdGlkeXZlcnNlKiogcGFja2FnZSwgYW5kIHBsdWcgaW4gdGhlIG5hbWUgb2YgdGhlIGZpbGUgaW4gcXVvdGVzIGluc2lkZSB0aGUgcGFyZW50aGVzZXMuIE1ha2Ugc3VyZSB5b3UgaW5jbHVkZSB0aGUgKi5jc3YqIGV4dGVuc2lvbi4KCmBgYHtyfQpjYS5wbSA8LSByZWFkX2NzdigiUG9saWN5TWFwIERhdGEgMjAyNS0wMy0yNyAxOTI1NTUgVVRDLmNzdiIsIHNraXAgPSAxKQpgYGAKClwKClRoZSBvcHRpb24gYHNraXAgPSAxYCB0ZWxscyBSIHRvIHNraXAgdGhlIGZpcnN0IHJvdyBvZiB0aGUgZmlsZSB3aGVuIGJyaW5naW5nIGl0IGluLiBUaGlzIGlzIGRvbmUgYmVjYXVzZSB0aGVyZSBhcmUgdHdvIHJvd3Mgb2YgY29sdW1uIG5hbWVzLiBUaGUgZmlyc3Qgcm93IGNvbnRhaW5zIHRoZSBleHRlbmRlZCB2ZXJzaW9uLCB3aGlsZSB0aGUgc2Vjb25kIGlzIHRoZSBhYnJpZGdlZCB2ZXJzaW9uLiBBYm92ZSB3ZSBrZWVwIHRoZSBhYnJpZGdlZCB2ZXJzaW9uLgoKWW91IHNob3VsZCBzZWUgYSB0aWJibGUgKmNhLnBtKiBwb3AgdXAgaW4geW91ciBFbnZpcm9ubWVudCB3aW5kb3cgKHRvcCByaWdodCkuIFdoYXQgZG9lcyBvdXIgZGF0YSBzZXQgbG9vayBsaWtlPwoKYGBge3J9CmdsaW1wc2UoY2EucG0pCmBgYAoKXAoKSWYgeW91IGxpa2Ugdmlld2luZyB5b3VyIGRhdGEgdGhyb3VnaCBhbiBFeGNlbCBzdHlsZSB3b3Jrc2hlZXQsIHR5cGUgaW4gYFZpZXcoY2EucG0pYCwgYW5kICpjYS5wbSogc2hvdWxkIHBvcCB1cCBpbiB0aGUgdG9wIGxlZnQgd2luZG93IG9mIHlvdXIgUiBTdHVkaW8gaW50ZXJmYWNlLgoKXAoKIyBNb3JlIGRhdGEgd3JhbmdsaW5nCgpXZSBsZWFybmVkIGFib3V0IHRoZSB2YXJpb3VzIGRhdGEgd3JhbmdsaW5nIHJlbGF0ZWQgZnVuY3Rpb25zIGZyb20gdGhlICoqdGlkeXZlcnNlKiogcGFja2FnZSBpbiBbTGFiIDFdKGxhYjEuaHRtbCkuIExldOKAmXMgZW1wbG95IHNvbWUgb2YgdGhvc2UgZnVuY3Rpb25zIHRvIGNyZWF0ZSBhIHNpbmdsZSBjb3VudHkgbGV2ZWwgZGF0YXNldCB0aGF0IGpvaW5zIHRoZSBkYXRhc2V0cyB3ZSBkb3dubG9hZGVkIGZyb20gdGhlIENlbnN1cyBBUEkgYW5kIFBvbGljeU1hcC4KCldlIGFyZSBnb2luZyB0byBjb21iaW5lIHRoZXNlIGRhdGFzZXRzIHVzaW5nIHRoZSBjb3VudHkgRklQUyBjb2Rlcy4gSW4gdGhlIENlbnN1cyBBUEkgYW5kIFBvbGljeU1hcCwgdGhlc2UgYXJlIGNvbnRhaW5lZCBpbiB0aGUgdmFyaWFibGVzIEdFT0lEIGFuZCBHZW9JRCwgcmVzcGVjdGl2ZWx5LiBMZXTigJlzIG1ha2Ugc3VyZSB0aGV5IGFyZSBpbiB0aGUgc2FtZSBjbGFzcy4KCmBgYHtyfQpjbGFzcyhjYS5wbSRHZW9JRCkKY2xhc3MoY2EkR0VPSUQpCmBgYAoKXAoKIyMgUGlwaW5nCgpPbmUgb2YgdGhlIGltcG9ydGFudCBpbm5vdmF0aW9ucyBmcm9tIHRoZSB0aWR5dmVyc2UgaXMgdGhlIHBpcGUgb3BlcmF0b3IgYCU+JWAuIFlvdSB1c2UgdGhlIHBpcGUgb3BlcmF0b3Igd2hlbiB5b3Ugd2FudCB0byBjb21iaW5lIG11bHRpcGxlIG9wZXJhdGlvbnMgaW50byBvbmUgbGluZSBvZiBjb250aW51b3VzIGNvZGUuIExldOKAmXMgY3JlYXRlIG91ciBmaW5hbCBkYXRhIG9iamVjdCAqY2Fjb3VudHkqIHVzaW5nIG91ciBicmFuZCBuZXcgZnJpZW5kIHRoZSBwaXBlLiBMZXQncyBwdXQgdG9nZXRoZXIgYSBsb3Qgb2Ygd2hhdCB3ZSd2ZSBkb25lIG92ZXIgdGhlIHBhc3QgZmV3IHdlZWtzIGludG8gb25lIHN0ZXAhIE5vdyB3ZSBhcmUgY29va2luZyB3aXRoIGdhcyEKCmBgYHtyfQpjYWNvdW50eSA8LSBjYSAlPiUgCiAgICAgIGxlZnRfam9pbihjYS5wbSwgYnkgPSBjKCJHRU9JRCIgPSAiR2VvSUQiKSkgJT4lCiAgICAgIG11dGF0ZShwd2hpdGUgPSBuaHdoaXRlRS90cG9wckUsIHBhc2lhbiA9IG5oYXNuRS90cG9wckUsIAogICAgICAgICAgICAgIHBibGFjayA9IG5oYmxrRS90cG9wckUsIHBoaXNwID0gaGlzcEUvdHBvcHJFLAogICAgICAgICAgICAgbWhpc3AgPSBjYXNlX3doZW4ocGhpc3AgPiAwLjUgfiAiTWFqb3JpdHkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICJOb3QgTWFqb3JpdHkiKSkgJT4lCiAgICAgIHJlbmFtZShDb3VudHkgPSBHZW9JRF9OYW1lKSAlPiUKICAgICAgc2VsZWN0KEdFT0lELCBDb3VudHksIHB3aGl0ZSwgcGFzaWFuLCBwYmxhY2ssIHBoaXNwLCBtaGlzcCwgbWhoaW5jKQpnbGltcHNlKGNhY291bnR5KQpgYGAKCkxldOKAmXMgYnJlYWsgZG93biB3aGF0IHRoZSBwaXBlIGlzIGRvaW5nIGhlcmUuIEZpcnN0LCB5b3Ugc3RhcnQgb3V0IHdpdGggeW91ciBkYXRhc2V0IGNhLiBZb3Ug4oCccGlwZeKAnSB0aGF0IGludG8gdGhlIGNvbW1hbmQgYGxlZnRfam9pbigpYC4gTm90aWNlIHRoYXQgeW91IGRpZG7igJl0IGhhdmUgdG8gdHlwZSBpbiBjYSBpbnNpZGUgdGhhdCBjb21tYW5kIC0gYCU+JWAgcGlwZXMgdGhhdCBpbiBmb3IgeW91LiBUaGUgY29tbWFuZCBqb2lucyB0aGUgZGF0YSBvYmplY3QgKmNhLnBtKiB0byAqY2EqLiBUaGUgcmVzdWx0IG9mIHRoaXMgZnVuY3Rpb24gZ2V0cyBwaXBlZCBpbnRvIHRoZSBgbXV0YXRlKClgIGZ1bmN0aW9uLCB3aGljaCBjcmVhdGVzIHRoZSBwZXJjZW50IHJhY2UvZXRobmljaXR5IChmcm9tIHRoZSBDZW5zdXMgQVBJKSwgYW5kIG1ham9yaXR5IEhpc3BhbmljIHZhcmlhYmxlcy4gVGhpcyBnZXRzIHBpcGVkIGludG8gdGhlIGByZW5hbWUoKWAgZnVuY3Rpb24sIHdoaWNoIHJlbmFtZXMgdGhlIGFtYmlndW91cyB2YXJpYWJsZSBuYW1lICpHZW9JRF9OYW1lKiB0byB0aGUgbW9yZSBkZXNjcmlwdGl2ZSBuYW1lICpDb3VudHkqLiBUaGlzIHRoZW4gZ2V0cyBwaXBlZCBpbnRvIHRoZSBmaW5hbCBmdW5jdGlvbiwgYHNlbGVjdCgpYCwgd2hpY2gga2VlcHMgdGhlIG5lY2Vzc2FyeSB2YXJpYWJsZXMuIEZpbmFsbHksIHRoZSBjb2RlIHNhdmVzIHRoZSByZXN1bHQgaW50byBjYWNvdW50eSB3aGljaCB3ZSBkZXNpZ25hdGVkIGF0IHRoZSBiZWdpbm5pbmcgd2l0aCB0aGUgYXJyb3cgb3BlcmF0b3IuCgpQaXBpbmcgbWFrZXMgY29kZSBjbGVhcmVyLCBhbmQgc2ltdWx0YW5lb3VzbHkgZ2V0cyByaWQgb2YgdGhlIG5lZWQgdG8gZGVmaW5lIGFueSBpbnRlcm1lZGlhdGUgb2JqZWN0cyB0aGF0IHlvdSB3b3VsZCBoYXZlIG5lZWRlZCB0byBrZWVwIHRyYWNrIG9mIHdoaWxlIHJlYWRpbmcgdGhlIGNvZGUuIFBJUEUsIFBpcGUsIGFuZCBwaXBlIHdoZW5ldmVyIHlvdSBjYW4uIFdlIG5lZWQgc29tZSBzdGlua2luIGJhZGdlcyEKClwKCiFbcGlwZSBCYWRnZSFdKHBpcGUucG5nKQoKXAoKIyMgU2F2aW5nIGRhdGEKCklmIHlvdSB3YW50IHRvIHNhdmUgeW91ciBkYXRhIGZyYW1lIG9yIHRpYmJsZSBhcyBhIGNzdiBmaWxlIG9uIHlvdXIgaGFyZCBkcml2ZSwgdXNlIHRoZSBjb21tYW5kIGB3cml0ZV9jc3YoKWAuIEJlZm9yZSB5b3Ugc2F2ZSBhIGZpbGUsIG1ha2Ugc3VyZSBSIGlzIHBvaW50ZWQgdG8gdGhlIGFwcHJvcHJpYXRlIGZvbGRlciBvbiB5b3VyIGhhcmQgZHJpdmUgYnkgdXNpbmcgdGhlIGZ1bmN0aW9uIGBnZXR3ZCgpYC4gSWYgaXTigJlzIG5vdCBwb2ludGVkIHRvIHRoZSByaWdodCBmb2xkZXIsIHVzZSB0aGUgZnVuY3Rpb24gYHNldHdkKClgIHRvIHNldCB0aGUgYXBwcm9wcmlhdGUgd29ya2luZyBkaXJlY3RvcnkuCgpgYGB7cn0Kd3JpdGVfY3N2KGNhY291bnR5LCAibGFiMl9maWxlLmNzdiIpCmBgYAoKVGhlIGZpcnN0IGFyZ3VtZW50IGlzIHRoZSBuYW1lIG9mIHRoZSBSIG9iamVjdCB5b3Ugd2FudCB0byBzYXZlLiBUaGUgc2Vjb25kIGFyZ3VtZW50IGlzIHRoZSBuYW1lIG9mIHRoZSBjc3YgZmlsZSBpbiBxdW90ZXMuIE1ha2Ugc3VyZSB0byBhZGQgdGhlIC5jc3YgZXh0ZW5zaW9uLiBUaGUgZmlsZSBpcyBzYXZlZCBpbiB5b3VyIGN1cnJlbnQgd29ya2luZyBkaXJlY3RvcnkuCgpcCgojIEV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMKClRoZSBmdW5jdGlvbnMgYWJvdmUgaGVscCB1cyBicmluZyBpbiBhbmQgY2xlYW4gZGF0YS4gVGhlIG5leHQgc2V0IG9mIGZ1bmN0aW9ucyBjb3ZlcmVkIGluIHRoaXMgc2VjdGlvbiB3aWxsIGhlbHAgdXMgc3VtbWFyaXplIHRoZSBkYXRhLiBEYXRhIHJlZmVyIHRvIHBpZWNlcyBvZiBpbmZvcm1hdGlvbiB0aGF0IGRlc2NyaWJlIGEgc3RhdHVzIG9yIGEgbWVhc3VyZSBvZiBtYWduaXR1ZGUuIEEgdmFyaWFibGUgaXMgYSBzZXQgb2Ygb2JzZXJ2YXRpb25zIG9uIGEgcGFydGljdWxhciBjaGFyYWN0ZXJpc3RpYy4gVGhlIGRpc3RyaWJ1dGlvbiBvZiBhIHZhcmlhYmxlIGlzIGEgbGlzdGluZyBzaG93aW5nIGFsbCB0aGUgcG9zc2libGUgdmFsdWVzIG9mIHRoZSBkYXRhIGZvciB0aGF0IHZhcmlhYmxlIGFuZCBob3cgb2Z0ZW4gdGhleSBvY2N1ci4gRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyAoRURBKSBlbmNvbXBhc3NlcyBhIHNldCBvZiBtZXRob2RzIChzb21lIHdvdWxkIHNheSBhIGZyYW1ld29yayBvciBwZXJzcGVjdGl2ZSkgZm9yIHN1bW1hcml6aW5nIGEgdmFyaWFibGXigJlzIGRpc3RyaWJ1dGlvbiwgYW5kIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgZGlzdHJpYnV0aW9ucyBvZiB0d28gb3IgbW9yZSB2YXJpYWJsZXMuIFdlIHdpbGwgY292ZXIgdHdvIGdlbmVyYWwgYXBwcm9hY2hlcyB0byBzdW1tYXJpemluZyB5b3VyIGRhdGE6IGRlc2NyaXB0aXZlIHN0YXRpc3RpY3MgYW5kIHZpc3VhbGl6YXRpb24gdmlhIGdyYXBocyBhbmQgY2hhcnRzLgoKXAoKIyMgRGVzY3JpcHRpdmUgc3RhdGlzdGljcwoKV2hlbiBkZXNjcmliaW5nIGEgZGlzdHJpYnV0aW9uLCB5b3VyIHF1YW50aXRhdGl2ZSBtZXNzYWdlIGlzIG9mdGVuIGJlc3QgY29tbXVuaWNhdGVkIGJ5IHJlZHVjaW5nIGRhdGEgdG8gYSBmZXcgc3VtbWFyeSBudW1iZXJzLiBUaGVzZSBudW1iZXJzIGFyZSBtZWFudCB0byBzdW1tYXJpemUgdGhlIOKAnHR5cGljYWzigJ0gdmFsdWUgaW4gdGhlIGRpc3RyaWJ1dGlvbiAoZS5nLiwgbWVhbiwgbWVkaWFuLCBtb2RlKSBhbmQgdGhlIHZhcmlhdGlvbiBvciDigJxzcHJlYWTigJ0gaW4gdGhlIGRpc3RyaWJ1dGlvbiAoZS5nLiwgbWluaW11bS9tYXhpbXVtLCBpbnRlcnF1YXJ0aWxlIHJhbmdlLCBzdGFuZGFyZCBkZXZpYXRpb24pLiBUaGVzZSBzdW1tYXJ5IG51bWJlcnMgYXJlIGtub3duIGFzIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3MuCgpXZSBjYW4gdXNlIHRoZSBmdW5jdGlvbiBzdW1tYXJpemUoKSB0byBnZXQgZGVzY3JpcHRpdmUgc3RhdGlzdGljcyBvZiBvdXIgZGF0YS4gRm9yIGV4YW1wbGUsIGxldOKAmXMgY2FsY3VsYXRlIHRoZSBtZWFuIGhvdXNlaG9sZCBpbmNvbWUgaW4gQ2FsaWZvcm5pYSBjb3VudGllcy4gVGhlIGZpcnN0IGFyZ3VtZW50IGluc2lkZSBzdW1tYXJpemUoKSBpcyB0aGUgZGF0YSBvYmplY3QgY2Fjb3VudHkgYW5kIHRoZSBzZWNvbmQgYXJndW1lbnQgaXMgdGhlIGZ1bmN0aW9uIGNhbGN1bGF0aW5nIHRoZSBzcGVjaWZpYyBzdW1tYXJ5IHN0YXRpc3RpYywgaW4gdGhpcyBjYXNlIG1lYW4oKS4KCmBgYHtyfQpjYWNvdW50eSAlPiUKICBzdW1tYXJpemUoTWVhbiA9IG1lYW4obWhoaW5jKSkKYGBgCgpUaGUgYXZlcmFnZSBjb3VudHkgbWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUgaXMgJDg3LDAwMS4gSWYgdGhlIHZhcmlhYmxlICptaGhpbmMqIGNvbnRhaW5lZCBtaXNzaW5nIHZhbHVlcywgd2Ugd291bGQgaGF2ZSBnb3R0ZW4gKk5BKiBhcyBhIHJlc3VsdC4gVG8gb21pdCBtaXNzaW5nIHZhbHVlcyBmcm9tIHRoZSBjYWxjdWxhdGlvbiwgeW91IG5lZWQgdG8gYWRkIGBybSA9IFRSVUVgIHRvIGBtZWFuKClgLgoKV2UgY2FuIGNhbGN1bGF0ZSBtb3JlIHRoYW4gb25lIHN1bW1hcnkgc3RhdGlzdGljIHdpdGhpbiBgc3VtbWFyaXplKClgLiBXaGF0IGlzIHRoZSBzcHJlYWQgb2YgdGhlIGRpc3RyaWJ1dGlvbj8gV2UgY2FuIGFkZCB0byBgc3VtbWFyaXplKClgIHRoZSBmdW5jdGlvbiBgc2QoKWAgdG8gY2FsY3VsYXRlIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24uClwKCmBgYHtyfQpjYWNvdW50eSAlPiUKICBzdW1tYXJpemUoTWVhbiA9IG1lYW4obWhoaW5jKSwgU0QgPSBzZChtaGhpbmMpKQpgYGAKClwKCkRvZXMgdGhlIGF2ZXJhZ2UgaW5jb21lIGRpZmZlciBieSBDYWxpZm9ybmlhIHJlZ2lvbj8gRmlyc3QsIGxldOKAmXMgY3JlYXRlIGEgbmV3IHZhcmlhYmxlIHJlZ2lvbiBkZXNpZ25hdGluZyBlYWNoIGNvdW50eSBhcyBCYXkgQXJlYSwgU291dGhlcm4gQ2FsaWZvcm5pYSwgQ2VudHJhbCBWYWxsZXksIENhcGl0YWwgUmVnaW9uIGFuZCB0aGUgUmVzdCBvZiBDYWxpZm9ybmlhIHVzaW5nIHRoZSBgY2FzZV93aGVuKClgIGZ1bmN0aW9uIHdpdGhpbiB0aGUgYG11dGF0ZSgpYCBmdW5jdGlvbi4KCmBgYHtyfQpjYWNvdW50eSA8LSBjYWNvdW50eSAlPiUKICAgIG11dGF0ZShyZWdpb24gPSBjYXNlX3doZW4oQ291bnR5ID09ICJTb25vbWEiIHwgQ291bnR5ID09ICJOYXBhIiB8IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb3VudHkgPT0gIlNvbGFubyIgfCBDb3VudHkgPT0gIk1hcmluIiB8IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb3VudHkgPT0gIkNvbnRyYSBDb3N0YSIgfCBDb3VudHkgPT0gIlNhbiBGcmFuY2lzY28iIHwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJTYW4gTWF0ZW8iIHwgQ291bnR5ID09ICJBbGFtZWRhIiB8IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb3VudHkgPT0gIlNhbnRhIENsYXJhIiB+ICJCYXkgQXJlYSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENvdW50eSA9PSAiSW1wZXJpYWwiIHwgQ291bnR5ID09ICJMb3MgQW5nZWxlcyIgfCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJPcmFuZ2UiIHwgQ291bnR5ID09ICJSaXZlcnNpZGUiIHwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJTYW4gRGllZ28iIHwgQ291bnR5ID09ICJTYW4gQmVybmFyZGlubyIgfAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb3VudHkgPT0gIlZlbnR1cmEiIH4gIlNvdXRoZXJuIENhbGlmb3JuaWEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb3VudHkgPT0gIkZyZXNubyIgfCBDb3VudHkgPT0gIk1hZGVyYSIgfCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJNYXJpcG9zYSIgfCBDb3VudHkgPT0gIk1lcmNlZCIgfCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJUdWxhcmUiIHwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENvdW50eSA9PSAiS2luZ3MiIH4gIkNlbnRyYWwgVmFsbGV5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJBbHBpbmUiIHwgQ291bnR5ID09ICJDb2x1c2EiIHwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJFbCBEb3JhZG8iIHwgQ291bnR5ID09ICJHbGVubiIgfAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb3VudHkgPT0gIlBsYWNlciIgfCBDb3VudHkgPT0gIlNhY3JhbWVudG8iIHwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJTdXR0ZXIiIHwgQ291bnR5ID09ICJZb2xvIiB8CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENvdW50eSA9PSAiWXViYSIgfiAiQ2FwaXRhbCBSZWdpb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gIlJlc3QiKSkKYGBgCgpcCgpOZXh0LCB3ZSBuZWVkIHRvIHBhaXIgYHN1bW1hcml6ZSgpYCB3aXRoIHRoZSBmdW5jdGlvbiBgZ3JvdXBfYnkoKWAuIFRoZSBmdW5jdGlvbiBgZ3JvdXBfYnkoKWAgdGVsbHMgUiB0byBydW4gc3Vic2VxdWVudCBmdW5jdGlvbnMgb24gdGhlIGRhdGEgb2JqZWN0IGJ5IGEgZ3JvdXAgY2hhcmFjdGVyaXN0aWMgKHN1Y2ggYXMgZ2VuZGVyLCBlZHVjYXRpb25hbCBhdHRhaW5tZW50LCBvciBpbiB0aGlzIGNhc2UsIHJlZ2lvbikuIFdl4oCZbGwgbmVlZCB0byB1c2Ugb3VyIG5ldyBiZXN0IGZyaWVuZCAlPiUgdG8gYWNjb21wbGlzaCB0aGlzIHRhc2suCgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ3JvdXBfYnkocmVnaW9uKSAlPiUKICBzdW1tYXJpemUoTWVhbiA9IG1lYW4obWhoaW5jKSkKYGBgCgpUaGUgZmlyc3QgcGlwZSBzZW5kcyBjYWNvdW50eSBpbnRvIHRoZSBmdW5jdGlvbiBgZ3JvdXBfYnkoKWAsIHdoaWNoIHRlbGxzIFIgdG8gZ3JvdXAgKmNhY291bnR5KiBieSB0aGUgdmFyaWFibGUgKnJlZ2lvbiouCgpIb3cgZG8geW91IGtub3cgdGhlIHRpYmJsZSBpcyBncm91cGVkPyBCZWNhdXNlIGl0IHRlbGxzIHlvdSEKCmBgYHtyfQpjYWNvdW50eSAlPiUKICBncm91cF9ieShyZWdpb24pIApgYGAKClwKClRoZSBzZWNvbmQgcGlwZSB0YWtlcyB0aGlzIGdyb3VwZWQgZGF0YXNldCBhbmQgc2VuZHMgaXQgaW50byB0aGUgYHN1bW1hcml6ZSgpYCBjb21tYW5kLCB3aGljaCBjYWxjdWxhdGVzIHRoZSBtZWFuIGluY29tZSAoYnkgcmVnaW9uLCBiZWNhdXNlIHRoZSBkYXRhc2V0IGlzIGdyb3VwZWQgYnkgcmVnaW9uKS4KClRvIGdldCB0aGUgbWVhbiwgbWVkaWFuIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gb2YgbWVkaWFuIGluY29tZSwgaXRzIGNvcnJlbGF0aW9uIHdpdGggcGVyY2VudCBIaXNwYW5pYywgYW5kIGdpdmUgY29sdW1uIGxhYmVscyBmb3IgdGhlIHZhcmlhYmxlcyBpbiB0aGUgcmVzdWx0aW5nIHN1bW1hcnkgdGFibGUsIHdlIHR5cGUgaW46CgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ3JvdXBfYnkocmVnaW9uKSAlPiUKICBzdW1tYXJpemUoTWVhbiA9IG1lYW4obWhoaW5jKSwKICAgICAgICAgICAgTWVkaWFuID0gbWVkaWFuKG1oaGluYyksCiAgICAgICAgICAgIFNEID0gc2QobWhoaW5jKSwKICAgICAgICAgICAgQ29ycmVsYXRpb24gPSBjb3IobWhoaW5jLCBwaGlzcCkpCmBgYAoKXAoKVGhlIHZhcmlhYmxlICptaGhpbmMqIGlzIG51bWVyaWMuIEhvdyBkbyB3ZSBzdW1tYXJpemUgY2F0ZWdvcmljYWwgdmFyaWFibGVzPyBXZSB1c3VhbGx5IHN1bW1hcml6ZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgYnkgZXhhbWluaW5nIGEgZnJlcXVlbmN5IHRhYmxlLiBUbyBnZXQgdGhlIHBlcmNlbnQgb2YgY291bnRpZXMgdGhhdCBoYXZlIGEgbWFqb3JpdHkgSGlzcGFuaWMgcG9wdWxhdGlvbiBtaGlzcCwgeW914oCZbGwgbmVlZCB0byBjb21iaW5lIHRoZSBmdW5jdGlvbnMgKmdyb3VwX2J5KCkqLCAqc3VtbWFyaXplKCkqIGFuZCAqbXV0YXRlKCkqIHVzaW5nIGAlPiVgLgoKYGBge3J9CmNhY291bnR5ICU+JQogIGdyb3VwX2J5KG1oaXNwKSAlPiUKICBzdW1tYXJpemUobiA9IG4oKSkgJT4lCiAgbXV0YXRlKGZyZXEgPSBuIC8gc3VtKG4pKQpgYGAKClwKClRoZSBjb2RlIGBncm91cF9ieShtaGlzcClgIHNlcGFyYXRlcyB0aGUgY291bnRpZXMgYnkgdGhlIGNhdGVnb3JpZXMgb2YgbWhpc3AgKE1ham9yaXR5LCBOb3QgTWFqb3JpdHkpLiBXZSB0aGVuIHVzZWQgYHN1bW1hcml6ZSgpYCB0byBjb3VudCB0aGUgbnVtYmVyIG9mIGNvdW50aWVzIHRoYXQgYXJlIE1ham9yaXR5IGFuZCBOb3QgTWFqb3JpdHkuIFRoZSBmdW5jdGlvbiB0byBnZXQgYSBjb3VudCBpcyBgbigpYCwgYW5kIHdlIHNhdmVkIHRoaXMgY291bnQgaW4gYSB2YXJpYWJsZSBuYW1lZCAqbiouIE5leHQsIHdlIHVzZWQgYG11dGF0ZSgpYCBvbiB0aGlzIHRhYmxlIHRvIGdldCB0aGUgcHJvcG9ydGlvbiBvZiBjb3VudGllcyBieSBNYWpvcml0eSBIaXNwYW5pYyBkZXNpZ25hdGlvbi4gVGhlIGNvZGUgYHN1bShuKWAgYWRkcyB0aGUgdmFsdWVzIG9mICpuKi4gV2UgdGhlbiBkaXZpZGUgdGhlIHZhbHVlIG9mIGVhY2ggbiBieSB0aGlzIHN1bS4gVGhhdCB5aWVsZHMgdGhlIGZpbmFsIGZyZXF1ZW5jeSB0YWJsZS4KCkluc3RlYWQgb2YgY2FsY3VsYXRpbmcgZGVzY3JpcHRpdmUgc3RhdGlzdGljcyBvbmUgYXQgYSB0aW1lIHVzaW5nIGBzdW1tYXJpemUoKWAsIHlvdSBjYW4gb2J0YWluIGEgc2V0IG9mIHN1bW1hcnkgc3RhdGlzdGljcyBmb3Igb25lIG9yIGFsbCB0aGUgbnVtZXJpYyB2YXJpYWJsZXMgaW4geW91ciBkYXRhc2V0IHVzaW5nIHRoZSBgc3VtbWFyeSgpYCBmdW5jdGlvbi4KCmBgYHtyfQpzdW1tYXJ5KGNhY291bnR5KQpgYGAKClwKCiMjIFRhYmxlcyBmb3IgcHJlc2VudGF0aW9uCgpUaGUgb3V0cHV0IGZyb20gdGhlIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3Mgd2XigJl2ZSByYW4gc28gZmFyIGlzIG5vdCBwcmVzZW50YXRpb24gcmVhZHkuIEZvciBleGFtcGxlLCB0YWtpbmcgYSBzY3JlZW5zaG90IG9mIHRoZSBmb2xsb3dpbmcgcmVzdWx0cyB0YWJsZSBwcm9kdWNlcyB1bm5lY2Vzc2FyeSBpbmZvcm1hdGlvbiB0aGF0IGlzIGNvbmZ1c2luZyBhbmQgbWVzc3kuCgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ3JvdXBfYnkocmVnaW9uKSAlPiUKICBzdW1tYXJpemUoTWVhbiA9IG1lYW4obWhoaW5jKSwKICAgICAgICAgICAgTWVkaWFuID0gbWVkaWFuKG1oaGluYyksCiAgICAgICAgICAgIFNEID0gc2QobWhoaW5jKSwKICAgICAgICAgICAgQ29ycmVsYXRpb24gPSBjb3IobWhoaW5jLCBwaGlzcCkpCmBgYAoKRnVydGhlcm1vcmUsIHlvdSB3b3VsZCBsaWtlIHRvIHNob3cgYSB0YWJsZSwgc2F5LCBpbiBhIG1hbnVzY3JpcHQgdGhhdCBkb2VzIG5vdCByZXF1aXJlIHlvdSB0byB0YWtlIGEgc2NyZWVuc2hvdCBvciBjb3B5aW5nIGFuZCBwYXN0aW5nIGludG8gRXhjZWwsIGJ1dCBpbnN0ZWFkIGNhbiBiZSBwcm9kdWNlZCB2aWEgY29kZSwgdGhhdCB3YXkgaXQgY2FuIGJlIGZpeGVkIGlmIHRoZXJlIGlzIGFuIGlzc3VlLCBhbmQgaXMgcmVwcm9kdWNpYmxlLgoKT25lIHdheSBvZiBwcm9kdWNpbmcgcHJlc2VudGF0aW9uIHRhYmxlcyBpbiBSIGlzIHRocm91Z2ggdGhlICoqZmxleHRhYmxlKiogcGFja2FnZS4gRmlyc3QsIHlvdSB3aWxsIG5lZWQgdG8gc2F2ZSB0aGUgdGliYmxlIG9yIGRhdGEgZnJhbWUgb2YgcmVzdWx0cyBpbnRvIGFuIG9iamVjdC4gRm9yIGV4YW1wbGUsIGxldOKAmXMgc2F2ZSB0aGUgYWJvdmUgcmVzdWx0cyBpbnRvIGFuIG9iamVjdCBuYW1lZCAqcmVnaW9uLnN1bW1hcnkqCgpgYGB7cn0KcmVnaW9uLnN1bW1hcnkgPC0gY2Fjb3VudHkgJT4lCiAgZ3JvdXBfYnkocmVnaW9uKSAlPiUKICBzdW1tYXJpemUoTWVhbiA9IG1lYW4obWhoaW5jKSwKICAgICAgICAgICAgTWVkaWFuID0gbWVkaWFuKG1oaGluYyksCiAgICAgICAgICAgIFNEID0gc2QobWhoaW5jKSwKICAgICAgICAgICAgQ29ycmVsYXRpb24gPSBjb3IobWhoaW5jLCBwaGlzcCkpCmBgYAoKWW91IHRoZW4gaW5wdXQgdGhlIG9iamVjdCBpbnRvIHRoZSBmdW5jdGlvbiBgZmxleHRhYmxlKClgLiBTYXZlIGl0IGludG8gYW4gb2JqZWN0IGNhbGxlZCAqbXlfdGFibGUqCgpgYGB7cn0KbXlfdGFibGUgPC0gZmxleHRhYmxlKHJlZ2lvbi5zdW1tYXJ5KQpteV90YWJsZQpgYGAKClwKCllvdSBzaG91bGQgc2VlIGEgcmVsYXRpdmVseSBjbGVhbiB0YWJsZSBwb3AgdXAgZWl0aGVyIGluIHlvdXIgY29uc29sZSBvciBWaWV3ZXIgd2luZG93LgoKXAoKV2hhdCBraW5kIG9mIG9iamVjdCBpcyAqbXlfdGFibGUqPwpgYGB7cn0KY2xhc3MobXlfdGFibGUpCmBgYAoKXAoKQWZ0ZXIgZG9pbmcgdGhpcywgd2UgY2FuIHByb2dyZXNzaXZlbHkgcGlwZSB0aGUgKm15X3RhYmxlKiBvYmplY3QgdGhyb3VnaCBtb3JlICoqZmxleHRhYmxlKiogZm9ybWF0dGluZyBmdW5jdGlvbnMuIEZvciBleGFtcGxlLCB5b3UgY2FuIGNoYW5nZSB0aGUgY29sdW1uIGhlYWRlciBuYW1lcyB1c2luZyB0aGUgZnVuY3Rpb24gYHNldF9oZWFkZXJfbGFiZWxzKClgIGFuZCBjZW50ZXIgdGhlIGhlYWRlciBuYW1lcyB1c2luZyB0aGUgZnVuY3Rpb24gYGFsaWduKClgLgoKYGBge3J9Cm15X3RhYmxlIDwtIG15X3RhYmxlICU+JQogICAgICAgICAgc2V0X2hlYWRlcl9sYWJlbHMoCiAgICAgICAgICAgIHJlZ2lvbiA9ICJSZWdpb24iLAogICAgICAgICAgICBNZWFuID0gIk1lYW4iLAogICAgICAgICAgICBNZWRpYW4gPSAiTWVkaWFuIiwKICAgICAgICAgICAgU0QgPSAiU3RhbmRhcmQgRGV2aWF0aW9uIiwKICAgICAgICAgICAgQ29ycmVsYXRpb24gPSAiQ29ycmVsYXRpb24iKSAlPiUKICAgICAgICAgICAgICBmbGV4dGFibGU6OmFsaWduKGFsaWduID0gImNlbnRlciIsIHBhcnQgPSAiYWxsIikKCm15X3RhYmxlCmBgYAoKV2VsbCBkb2Vzbid0IHRoYXQgbG9vayBzcGlmZnkhIFRoZXJlIGFyZSBhIHNsZXcgb2Ygb3B0aW9ucyBmb3IgZm9ybWF0dGluZyB5b3VyIHRhYmxlLCBpbmNsdWRpbmcgYWRkaW5nIGZvb3Rub3RlcywgYm9yZGVycywgc2hhZGUgYW5kIG90aGVyIGZlYXR1cmVzLiBDaGVjayBvdXQgdGhpcyBbdXNlZnVsIHR1dG9yaWFsXShodHRwczovL2FyZGF0YS1mci5naXRodWIuaW8vZmxleHRhYmxlLWJvb2svKSBmb3IgYW4gZXhwbGFuYXRpb24gb2Ygc29tZSBvZiB0aGVzZSBmZWF0dXJlcy4KCk9uY2UgeW914oCZcmUgZG9uZSBmb3JtYXR0aW5nIHlvdXIgdGFibGUsIHlvdSBjYW4gdGhlbiBleHBvcnQgaXQgdG8gV29yZCwgUG93ZXJQb2ludCBvciBIVE1MIG9yIGFzIGFuIGltYWdlIChQTkcpIGZpbGVzLiBUbyBkbyB0aGlzLCB1c2Ugb25lIG9mIHRoZSBmb2xsb3dpbmcgZnVuY3Rpb25zOiBgc2F2ZV9hc19kb2N4KClgLCBgc2F2ZV9hc19wcHR4KClgLCBgc2F2ZV9hc19pbWFnZSgpYCwgYW5kIGBzYXZlX2FzX2h0bWwoKWAuIAoKVXNlIHRoZSBgc2F2ZV9hc19pbWFnZSgpYCBmdW5jdGlvbiB0byBzYXZlIHlvdXIgdGFibGUgYXMgYW4gaW1hZ2UuCgpgYGB7cn0Kc2F2ZV9hc19pbWFnZShteV90YWJsZSwgcGF0aCA9ICJyZWdfaW5jb21lLnBuZyIpCmBgYAoKWW91IGZpcnN0IHB1dCBpbiB0aGUgdGFibGUgbXlfdGFibGUsIGFuZCBzZXQgdGhlIGZpbGUgbmFtZSB3aXRoIHRoZSAucG5nIGV4dGVuc2lvbi4gQ2hlY2sgeW91ciB3b3JraW5nIGRpcmVjdG9yeS4gWW91IHNob3VsZCBzZWUgdGhlIGZpbGUgKnJlZ19pbmNvbWUucG5nKi4KClwKCiMjIERhdGEgdmlzdWFsaXphdGlvbgoKQW5vdGhlciB3YXkgb2Ygc3VtbWFyaXppbmcgdmFyaWFibGVzIGFuZCB0aGVpciByZWxhdGlvbnNoaXBzIGlzIHRocm91Z2ggZ3JhcGhzIGFuZCBjaGFydHMuIFRoZSBtYWluIHBhY2thZ2UgZm9yIFIgZ3JhcGhpbmcgaXMgKmdncGxvdDIqIHdoaWNoIGlzIGEgcGFydCBvZiB0aGUgKnRpZHl2ZXJzZSogcGFja2FnZS4gVGhlIGdyYXBoaW5nIGZ1bmN0aW9uIGlzIGBnZ3Bsb3QoKWAgYW5kIGl0IHRha2VzIG9uIHRoZSBiYXNpYyB0ZW1wbGF0ZQpgYGB7ciwgZXZhbD1GQUxTRX0KZ2dwbG90KGRhdGEgPSA8REFUQT4pICsKICAgICAgPEdFT01fRlVOQ1RJT04+KG1hcHBpbmcgPSBhZXMoeCwgeSkpICsKICAgICAgPE9QVElPTlM+KCkKYGBgCgpcCgoxLiBgZ2dwbG90KClgIGlzIHRoZSBiYXNlIGZ1bmN0aW9uIHdoZXJlIHlvdSBzcGVjaWZ5IHlvdXIgZGF0YXNldCB1c2luZyB0aGUgZGF0YSA9IDxEQVRBPiBhcmd1bWVudC4KMi4gWW91IHRoZW4gbmVlZCB0byBidWlsZCBvbiB0aGlzIGJhc2UgYnkgdXNpbmcgdGhlIHBsdXMgb3BlcmF0b3IgKyBhbmQgPEdFT01fRlVOQ1RJT04+KCkgd2hlcmUgPEdFT01fRlVOQ1RJT04+KCkgaXMgYSB1bmlxdWUgZ2VvbSBmdW5jdGlvbiBpbmRpY2F0aW5nIHRoZSB0eXBlIG9mIGdyYXBoIHlvdSB3YW50IHRvIHBsb3QuIEVhY2ggdW5pcXVlIGZ1bmN0aW9uIGhhcyBpdHMgdW5pcXVlIHNldCBvZiBtYXBwaW5nIGFyZ3VtZW50cyB3aGljaCB5b3Ugc3BlY2lmeSB1c2luZyB0aGUgbWFwcGluZyA9IGFlcygpIGFyZ3VtZW50LiBDaGFydHMgYW5kIGdyYXBocyBoYXZlIGFuIHgtYXhpcywgeS1heGlzLCBvciBib3RoLiBDaGVjayB0aGlzIGdncGxvdCBjaGVhdCBzaGVldCBmb3IgYWxsIHBvc3NpYmxlIGdlb21zLgozLiBgPE9QVElPTlM+KClgIGFyZSBhIHNldCBvZiBmdW5jdGlvbnMgeW91IGNhbiBzcGVjaWZ5IHRvIGNoYW5nZSB0aGUgbG9vayBvZiB0aGUgZ3JhcGgsIGZvciBleGFtcGxlIHJlbGFiZWxpbmcgdGhlIGF4ZXMgb3IgYWRkaW5nIGEgdGl0bGUuCgpUaGUgYmFzaWMgaWRlYSBpcyB0aGF0IGEgZ2dwbG90IGdyYXBoaWMgbGF5ZXJzIGdlb21ldHJpYyBvYmplY3RzIChjaXJjbGVzLCBsaW5lcywgZXRjKSwgdGhlbWVzLCBhbmQgc2NhbGVzIG9uIHRvcCBvZiBkYXRhLgoKWW91IGZpcnN0IHN0YXJ0IG91dCB3aXRoIHRoZSBiYXNlIGxheWVyLiBJdCByZXByZXNlbnRzIHRoZSBlbXB0eSAqZ2dwbG90KiBsYXllciBkZWZpbmVkIGJ5IHRoZSBgZ2dwbG90KClgIGZ1bmN0aW9uLgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ2dwbG90KCkKYGBgCgpXZSBnZXQgYW4gZW1wdHkgcGxvdC4gV2UgaGF2ZW7igJl0IHRvbGQgYGdncGxvdCgpYCB3aGF0IHR5cGUgb2YgZ2VvbWV0cmljIG9iamVjdChzKSB3ZSB3YW50IHRvIHBsb3QsIG5vciBob3cgdGhlIHZhcmlhYmxlcyBzaG91bGQgYmUgbWFwcGVkIHRvIHRoZSBnZW9tZXRyaWMgb2JqZWN0cywgc28gd2UganVzdCBoYXZlIGEgYmxhbmsgcGxvdC4gV2UgaGF2ZSBgZ2VvbXNgIHRvIHBhaW50IHRoZSBibGFuayBjYW52YXMuCgpGcm9tIGhlcmUsIHdlIGFkZCBhIOKAnGBnZW9tYOKAnSBsYXllciB0byB0aGUgZ2dwbG90IG9iamVjdC4gTGF5ZXJzIGFyZSBhZGRlZCB0byBnZ3Bsb3Qgb2JqZWN0cyB1c2luZyBgK2AsIGluc3RlYWQgb2YgYCU+JWAsIHNpbmNlIHlvdSBhcmUgbm90IGV4cGxpY2l0bHkgcGlwaW5nIGFuIG9iamVjdCBpbnRvIGVhY2ggc3Vic2VxdWVudCBsYXllciwgYnV0IGFkZGluZyBsYXllcnMgb24gdG9wIG9mIG9uZSBhbm90aGVyLiBFYWNoIGBnZW9tYCBpcyBhc3NvY2lhdGVkIHdpdGggYSBzcGVjaWZpYyB0eXBlIG9mIGdyYXBoLgoKTGV04oCZcyBnbyB0aHJvdWdoIHNvbWUgb2YgdGhlIG1vcmUgY29tbW9uIGFuZCBwb3B1bGFyIGdyYXBocyBmb3IgdmlzdWFsaXppbmcgeW91ciBkYXRhLgoKXAoKIyMjIEhpc3RvZ3JhbQoKQSB0eXBpY2FsIHZpc3VhbCBmb3Igc3VtbWFyaXppbmcgYSBzaW5nbGUgbnVtZXJpYyB2YXJpYWJsZSBpcyBhIGhpc3RvZ3JhbS4gVG8gY3JlYXRlIGEgaGlzdG9ncmFtLCB1c2UgYGdlb21faGlzdG9ncmFtKClgIGZvciA8R0VPTV9GVU5DVElPTigpPi4gTGV04oCZcyBjcmVhdGUgYSBoaXN0b2dyYW0gb2YgbWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUuIE5vdGUgdGhhdCB3ZSBkb27igJl0IG5lZWQgdG8gc3BlY2lmeSB0aGUgYHk9YCBoZXJlIGJlY2F1c2Ugd2UgYXJlIHBsb3R0aW5nIG9ubHkgb25lIHZhcmlhYmxlLiBXZSBwaXBlIGluIHRoZSBvYmplY3QgKmNhY291bnR5KiBpbnRvIGBnZ3Bsb3QoKWAgdG8gZXN0YWJsaXNoIHRoZSBiYXNlIGxheWVyLiBXZSB0aGVuIHVzZSBgZ2VvbV9oaXN0b2dyYW0oKWAgdG8gYWRkIHRoZSBkYXRhIGxheWVyIG9uIHRvcCBvZiB0aGUgYmFzZS4KCmBgYHtyfQpjYWNvdW50eSAlPiUKICBnZ3Bsb3QoKSArIAogIGdlb21faGlzdG9ncmFtKG1hcHBpbmcgPSBhZXMoeD1taGhpbmMpKSAKYGBgCgpXZSBjYW4gY29udGludWUgdG8gYWRkIGxheWVycyB0byB0aGUgcGxvdC4gRm9yIGV4YW1wbGUsIHdlIHVzZSB0aGUgYXJndW1lbnQgYHhsYWIoIk1lZGlhbiBob3VzZWhvbGQgaW5jb21lIilgIHRvIGxhYmVsIHRoZSB4LWF4aXMgYXMg4oCcTWVkaWFuIGhvdXNlaG9sZCBpbmNvbWXigJ0uCgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ2dwbG90KCkgKyAKICBnZW9tX2hpc3RvZ3JhbShtYXBwaW5nID0gYWVzKHg9bWhoaW5jKSkgKwogIHhsYWIoIk1lZGlhbiBob3VzZWhvbGQgaW5jb21lIikKYGBgCgpOb3RlIHRoZSBtZXNzYWdlIHByb2R1Y2VkIHdpdGggdGhlIHBsb3QuIEl0IHRlbGxzIHVzIHRoYXQgd2UgY2FuIHVzZSB0aGUgYGJpbnMgPWAgYXJndW1lbnQgdG8gY2hhbmdlIHRoZSBudW1iZXIgb2YgYmlucyB1c2VkIHRvIHByb2R1Y2UgdGhlIGhpc3RvZ3JhbS4gWW91IGNhbiBpbmNyZWFzZSB0aGUgbnVtYmVyIG9mIGJpbnMgdG8gbWFrZSB0aGUgYmlucyBuYXJyb3dlciBhbmQgdGh1cyBnZXQgYSBmaW5lciBncmFpbiBvZiBkZXRhaWwuIE9yIHlvdSBjYW4gZGVjcmVhc2UgdGhlIG51bWJlciBvZiBiaW5zIHRvIGdldCBhIGJyb2FkZXIgdmlzdWFsIHN1bW1hcnkgb2YgdGhlIHNoYXBlIG9mIHRoZSB2YXJpYWJsZeKAmXMgZGlzdHJpYnV0aW9uLiBDb21wYXJlIGJpbnMgPSAxMCB0byBiaW5zID0gNTAuCgpcCgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ2dwbG90KCkgKyAKICBnZW9tX2hpc3RvZ3JhbShtYXBwaW5nID0gYWVzKHg9bWhoaW5jKSwgYmlucz0xMCkgKwogIHhsYWIoIk1lZGlhbiBob3VzZWhvbGQgaW5jb21lIikKYGBgCgpcCgojIyMgQm94cGxvdAoKV2UgY2FuIHVzZSBhICpib3hwbG90KiB0byB2aXN1YWxseSBzdW1tYXJpemUgdGhlIGRpc3RyaWJ1dGlvbiBvZiBhIHNpbmdsZSBudW1lcmljIHZhcmlhYmxlIG9yIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBhIGNhdGVnb3JpY2FsIGFuZCBudW1lcmljIHZhcmlhYmxlLiBVc2UgYGdlb21fYm94cGxvdCgpYCBmb3IgPEdFT01fRlVOQ1RJT04oKT4gdG8gY3JlYXRlIGEgYm94cGxvdC4gTGV04oCZcyBleGFtaW5lIG1lZGlhbiBob3VzZWhvbGQgaW5jb21lLiBOb3RlIHRoYXQgYSBib3hwbG90IHVzZXMgeT0gcmF0aGVyIHRoYW4geD0gdG8gc3BlY2lmeSB3aGVyZSBtaGhpbmMgZ29lcy4gV2UgYWxzbyBwcm92aWRlIGEgZGVzY3JpcHRpdmUgeS1heGlzIGxhYmVsIHVzaW5nIHRoZSBgeWxhYigpYCBmdW5jdGlvbi4KCmBgYHtyfQpjYWNvdW50eSAlPiUKICBnZ3Bsb3QoKSArCiAgICBnZW9tX2JveHBsb3QobWFwcGluZyA9IGFlcyh5ID0gbWhoaW5jKSkgKwogICAgeWxhYigiTWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUiKQpgYGAKClwKCkxldOKAmXMgZXhhbWluZSB0aGUgZGlzdHJpYnV0aW9uIG9mIG1lZGlhbiBpbmNvbWUgYnkgKm1oaXNwLiogQmVjYXVzZSB3ZSBhcmUgZXhhbWluaW5nIHRoZSBhc3NvY2lhdGlvbiBiZXR3ZWVuIHR3byB2YXJpYWJsZXMsIHdlIG5lZWQgdG8gc3BlY2lmeSB4IGFuZCB5IHZhcmlhYmxlcyBpbiBgYWVzKClgICh3ZSBhbHNvIHNwZWNpZnkgYm90aCB4LSBhbmQgeS1heGlzIGxhYmVscykuCgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ2dwbG90KCkgKwogICAgZ2VvbV9ib3hwbG90KG1hcHBpbmcgPSBhZXMoeCA9IG1oaXNwLCB5ID0gbWhoaW5jKSkgKwogICAgeGxhYigiTWFqb3JpdHkgSGlzcGFuaWMiKSArCiAgICB5bGFiKCJNZWRpYW4gaG91c2Vob2xkIGluY29tZSIpCmBgYAoKVGhlIHRvcCBhbmQgYm90dG9tIG9mIGEgYm94cGxvdCByZXByZXNlbnQgdGhlIDc1dGggYW5kIDI1dGggcGVyY2VudGlsZXMsIHJlc3BlY3RpdmVseS4gVGhlIGxpbmUgaW4gdGhlIG1pZGRsZSBvZiB0aGUgYm94IGlzIHRoZSA1MHRoIHBlcmNlbnRpbGUuIFBvaW50cyBvdXRzaWRlIHRoZSB3aGlza2VycyByZXByZXNlbnQgb3V0bGllcnMuIE91dGxpZXJzIGFyZSBkZWZpbmVkIGFzIGhhdmluZyB2YWx1ZXMgdGhhdCBhcmUgZWl0aGVyIGxhcmdlciB0aGFuIHRoZSA3NXRoIHBlcmNlbnRpbGUgcGx1cyAxLjUgdGltZXMgdGhlIElRUiBvciBzbWFsbGVyIHRoYW4gdGhlIDI1dGggcGVyY2VudGlsZSBtaW51cyAxLjUgdGltZXMgdGhlIElRUi4KClwKClRoZSBib3hwbG90IGlzIGZvciBhbGwgY291bnRpZXMgY29tYmluZWQuIFVzZSB0aGUgYGZhY2V0X3dyYXAoKWAgZnVuY3Rpb24gdG8gc2VwYXJhdGUgYnkgcmVnaW9uLiBOb3RpY2UgdGhlIHRpbGRlIH4gYmVmb3JlIHRoZSB2YXJpYWJsZSByZWdpb24gaW5zaWRlIGBmYWNldF93cmFwKClgLgoKYGBge3J9CmNhY291bnR5ICU+JQogIGdncGxvdCgpICsKICAgIGdlb21fYm94cGxvdChtYXBwaW5nID0gYWVzKHggPSBtaGlzcCwgeSA9IG1oaGluYykpICsKICAgIHhsYWIoIk1ham9yaXR5IEhpc3BhbmljIikgKwogICAgeWxhYigiTWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUiKSArCiAgICBmYWNldF93cmFwKH5yZWdpb24pIApgYGAKClwKCiMjIyBCYXIgY2hhcnQKClRoZSBwcmltYXJ5IHB1cnBvc2Ugb2YgYSBiYXIgY2hhcnQgaXMgdG8gaWxsdXN0cmF0ZSBhbmQgY29tcGFyZSB0aGUgdmFsdWVzIGZvciBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlLiBCYXIgY2hhcnRzIHNob3cgZWl0aGVyIHRoZSBudW1iZXIgb3IgZnJlcXVlbmN5IG9mIGVhY2ggY2F0ZWdvcnkuIFRvIGNyZWF0ZSBhIGJhciBjaGFydCwgdXNlIGBnZW9tX2JhcigpYCBmb3IgPEdFT01fRlVOQ1RJT04+KCkuIExldOKAmXMgc2hvdyBhIGJhciBjaGFydCBvZiBtZWRpYW4gaG91c2Vob2xkIGluY29tZSBieSByZWdpb24uIFdl4oCZbGwgYm9ycm93IGZyb20gY29kZSBhYm92ZSB0aGF0IGdlbmVyYXRlZCBhIHRpYmJsZSBvZiBtZWFuIGhvdXNlaG9sZCBpbmNvbWUgYnkgcmVnaW9uLCBhbmQgcGlwZSB0aGF0IGludG8gYGdncGxvdCgpYC4KCmBgYHtyfQpjYWNvdW50eSAlPiUKICBncm91cF9ieShyZWdpb24pICU+JQogIHN1bW1hcml6ZShNZWFuID0gbWVhbihtaGhpbmMpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9cmVnaW9uLCB5ID0gTWVhbikpICsKICBnZW9tX2JhcihzdGF0ID0gIklkZW50aXR5IikgKwogIHhsYWIoIlJlZ2lvbiIpICsKICB5bGFiKCJBdmVyYWdlIGhvdXNlaG9sZCBpbmNvbWUiKQpgYGAKClwKClJpZ2h0IG5vdyB0aGUgYmFycyBhcmUgb3JkZXJlZCBiYXNlZCBvbiB0aGUgcmVnaW9uIG5hbWVzLiBXZSBjYW4gb3JkZXIgdGhlIGJhcnMgaW4gZGVzY2VuZGluZyBvcmRlciBiYXNlZCBvbiBob3VzZWhvbGQgaW5jb21lIGJ5IHVzaW5nIHRoZSBgcmVvcmRlcigpYCBmdW5jdGlvbi4gTm90aWNlIHRoZSBuZWdhdGl2ZSBzaWduIGluIGZyb250IG9mIE1lYW4gdG8gb3JkZXIgYnkgZGVzY2VuZGluZyBvcmRlci4KCmBgYHtyfQpjYWNvdW50eSAlPiUKICBncm91cF9ieShyZWdpb24pICU+JQogIHN1bW1hcml6ZShNZWFuID0gbWVhbihtaGhpbmMpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9cmVvcmRlcihyZWdpb24sIC1NZWFuKSwgeSA9IE1lYW4pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJJZGVudGl0eSIpICsKICB4bGFiKCJSZWdpb24iKSArCiAgeWxhYigiQXZlcmFnZSBob3VzZWhvbGQgaW5jb21lIikKYGBgCgpcCgpXZSBjYW4gZmxpcCB0aGUgYXhlcyB1c2luZyB0aGUgZnVuY3Rpb24gYGNvb3JkX2ZsaXAoKWAuCgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ3JvdXBfYnkocmVnaW9uKSAlPiUKICBzdW1tYXJpemUoTWVhbiA9IG1lYW4obWhoaW5jKSkgJT4lCiAgZ2dwbG90KGFlcyh4PXJlb3JkZXIocmVnaW9uLCAtTWVhbiksIHkgPSBNZWFuKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiSWRlbnRpdHkiKSArCiAgeGxhYigiUmVnaW9uIikgKwogIHlsYWIoIkF2ZXJhZ2UgaG91c2Vob2xkIGluY29tZSIpICsKICBjb29yZF9mbGlwKCkKYGBgCgpcCgpXZWxsIGFyZW4ndCB3ZSBmYW5jeT8gYGdncGxvdCgpYCBpcyBhIHBvd2VyZnVsIGZ1bmN0aW9uLCBhbmQgeW91IGNhbiBtYWtlIGEgbG90IG9mIHZpc3VhbGx5IGNhcHRpdmF0aW5nIGdyYXBocy4gV2UgaGF2ZSBqdXN0IHNjcmF0Y2hlZCB0aGUgc3VyZmFjZSBvZiBpdHMgZnVuY3Rpb25zIGFuZCBmZWF0dXJlcy4gWW91IGNhbiBhbHNvIG1ha2UgeW91ciBncmFwaHMgcmVhbGx5IOKAnHByZXR0eeKAnSBhbmQgcHJvZmVzc2lvbmFsIGxvb2tpbmcgYnkgYWx0ZXJpbmcgZ3JhcGhpbmcgZmVhdHVyZXMsIGluY2x1ZGluZyBjb2xvcnMsIGxhYmVscywgdGl0bGVzIGFuZCBheGVzLiBGb3IgYSBsaXN0IG9mIGBnZ3Bsb3QoKWAgZnVuY3Rpb25zIHRoYXQgYWx0ZXIgdmFyaW91cyBmZWF0dXJlcyBvZiBhIGdyYXBoLCBjaGVjayBvdXQgQ2hhcHRlciAyOCBpbiBbUkRTXShodHRwczovL3I0ZHMuaGFkbGV5Lm56LykuCgpIZXJl4oCZcyB5b3VyICoqZ2dwbG90MioqIGJhZGdlLiBXZWFyIGl0IHdpdGggcHJpZGUhIE9LLCB3ZSd2ZSBkb25lIGEgbG90IHRvZGF5LiBHZXQgb3V0c2lkZSBhbmQgZ2V0IHNvbWUgZnJlc2ggYWlyIQoKXAoKIVtnZ3Bsb3QyIEJhZGdlXShnZ3Bsb3QyLnBuZyl7d2lkdGg9MjAlLCBoZWlnaHQ9MjAlfQoKClwKCiMgT3RoZXIgVVMgR292ZXJubWVudCBkYXRhc2V0cwoKQ2hlY2sgb3V0IHRoZSBbRGF0YSBTb3VyY2VzXShPdGhlci5odG1sKSBsaW5rIGZvciBtb3JlIGxpbmtzIHRvIFVTIEdvdmVybm1lbnQgRGF0YQoKXAoKIyBBY2tub3dsZWRnZW1lbnRzCgpNYWpvciBhY2tub3dsZWRnZW1lbnRzIHRvIE5vbGkgQnJhemlsIChhcyBhbHdheXMpIGFuZCBbQ3JpbWUgYnkgdGhlIE51bWJlcnNdKGh0dHBzOi8vY3JpbWVieXRoZW51bWJlcnMuY29tL2dlb2NvZGluZy5odG1sKS4gCgoK